Database Seeding
Introduction
Database seeding is the process of populating your database with initial or test data. In Slenix, seeders are simple PHP classes that insert records into your tables. They are especially useful for:
- Populating a fresh database with the data your application needs to function (roles, categories, settings)
- Generating large volumes of realistic fake data for development and testing
- Sharing a reproducible dataset with your team so everyone works with the same starting point
Slenix ships with three tools for seeding:
- Seeders — classes that orchestrate what data goes into the database
- Factories — classes that define how to generate realistic fake data for a model
- Fake — a static helper with 40+ methods for generating names, emails, dates, addresses, and more — no external packages needed
Directory Structure
database/
├── migrations/ ← Schema version control
├── seeds/ ← Seeder classes
│ ├── DatabaseSeeder.php ← Entry point (always executed by default)
│ ├── UserSeeder.php
│ └── CategorySeeder.php
└── factories/ ← Factory classes
├── UserFactory.php
└── PostFactory.phpRunning Seeders
Run the Default Seeder
php celestial db:seedThis always runs DatabaseSeeder, which is the entry point where you orchestrate all other seeders.
Run a Specific Seeder
php celestial db:seed --class=UserSeeder
php celestial db:seed --class=CategorySeederFresh Migration + Seed
The most common workflow during development: drop everything, rebuild the schema, and fill with data:
php celestial migrate:fresh --seedThis is equivalent to running migrate:fresh followed by db:seed.
Creating Seeders
Generate a Seeder File
php celestial make:seeder UserSeeder
php celestial make:seeder CategorySeederGenerated files are placed in database/seeds/. The name Seeder is appended automatically if omitted.
Seeder Structure
Each seeder must extend Seeder and implement the run() method:
<?php
declare(strict_types=1);
use Slenix\Supports\Database\Seeds\Seeder;
use Slenix\Supports\Database\Seeds\Fake;
class UserSeeder extends Seeder
{
public function run(): void
{
// Your seeding logic here
}
}The DatabaseSeeder
DatabaseSeeder is the central coordinator. It runs when you execute db:seed without specifying a class. Register every seeder you want to run here using $this->call():
<?php
declare(strict_types=1);
use Slenix\Supports\Database\Seeds\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call([
CategorySeeder::class, // must run before PostSeeder (FK dependency)
UserSeeder::class,
PostSeeder::class,
CommentSeeder::class,
]);
}
}Order matters. Always seed parent tables before child tables. For example, seed
categoriesbeforeproductsifproductshas acategory_idforeign key.
Writing Seeders
Inserting Data Directly
The simplest approach — hardcode the records you need:
class CategorySeeder extends Seeder
{
public function run(): void
{
$categories = [
['name' => 'Technology', 'slug' => 'technology'],
['name' => 'Science', 'slug' => 'science'],
['name' => 'Business', 'slug' => 'business'],
['name' => 'Health', 'slug' => 'health'],
];
$this->insertBatch('categories', $categories);
}
}Inserting Without Duplicates
Use insertOrIgnore() when running the seeder multiple times should not create duplicate records:
class RoleSeeder extends Seeder
{
public function run(): void
{
foreach (['admin', 'editor', 'viewer'] as $role) {
$this->insertOrIgnore('roles', [
'name' => $role,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
}
}Generating Fake Data with Fake
For development datasets, use the Fake helper to generate realistic random data:
class UserSeeder extends Seeder
{
public function run(): void
{
$this->truncate('users'); // clean before seeding
$rows = [];
for ($i = 0; $i < 100; $i++) {
$name = Fake::name();
$rows[] = [
'name' => $name,
'email' => Fake::email($name),
'password' => Fake::hashedPassword('password'),
'role' => Fake::randomElement(['admin', 'editor', 'viewer']),
'is_active' => Fake::boolean(80), // 80% chance of true
'created_at' => Fake::dateTime('-1 year'),
'updated_at' => Fake::dateTime('-6 months'),
];
}
$inserted = $this->insertBatch('users', $rows);
echo " → {$inserted} users inserted\n";
}
}Using Models Directly
If your seeders need to trigger model events (hooks like creating, created), use the model's create() method:
use App\Models\User;
use App\Models\Post;
class PostSeeder extends Seeder
{
public function run(): void
{
// Get all existing user IDs
$stmt = $this->pdo->query("SELECT id FROM users");
$userIds = $stmt->fetchAll(\PDO::FETCH_COLUMN);
for ($i = 0; $i < 50; $i++) {
$title = Fake::title();
Post::create([
'title' => $title,
'slug' => Fake::slug($title),
'body' => Fake::paragraph(6),
'user_id' => Fake::randomElement($userIds),
'status' => Fake::randomElement(['draft', 'published']),
'created_at' => Fake::dateTime('-1 year'),
]);
}
}
}Calling Other Seeders
Inside any seeder you can call other seeders using $this->call():
class DatabaseSeeder extends Seeder
{
public function run(): void
{
// Call one seeder
$this->call(UserSeeder::class);
// Call multiple seeders in order
$this->call([
CategorySeeder::class,
ProductSeeder::class,
OrderSeeder::class,
]);
}
}Seeder API Reference
These methods are available inside every seeder via $this:
| Method | Description |
|---|---|
insertBatch($table, $rows, $chunk) | Inserts multiple rows efficiently. Default chunk: 500 |
insertOrIgnore($table, $data) | Inserts a single row, silently skips if duplicate |
truncate($table) | Clears the table (disables FK checks temporarily) |
exists($table, $conditions) | Returns true if a matching record already exists |
statement($sql, $bindings) | Executes raw SQL with optional bindings |
call($seederClass) | Runs another seeder class or array of seeders |
$this->pdo | Direct access to the PDO connection for custom queries |
Factories
Factories define how to generate realistic data for a model. They use the fluent API count(), state(), create(), make(), and raw().
Creating a Factory
php celestial make:factory UserFactory
php celestial make:factory PostFactoryFactory Structure
<?php
declare(strict_types=1);
use Slenix\Supports\Database\Seeds\Factory;
use Slenix\Supports\Database\Seeds\Fake;
use App\Models\User;
class UserFactory extends Factory
{
protected string $model = User::class;
public function definition(): array
{
$name = Fake::name();
return [
'name' => $name,
'email' => Fake::email($name),
'password' => Fake::hashedPassword('password'),
'role' => 'viewer',
'is_active' => true,
'created_at' => Fake::dateTime('-1 year'),
'updated_at' => Fake::dateTime(),
];
}
}Using Factories
// Create and persist 1 user (returns model instance)
$user = UserFactory::new()->create();
// Create and persist 10 users (returns array of instances)
$users = UserFactory::new()->count(10)->create();
// Override specific attributes
$admin = UserFactory::new()->create(['role' => 'admin']);
// Create without persisting to the database
$user = UserFactory::new()->make();
// Get just the attribute array (no model, no DB)
$data = UserFactory::new()->raw();
// ['name' => 'Ana Silva', 'email' => 'ana123@gmail.com', ...]Factory States
States let you define named variations of a model with different attributes:
class UserFactory extends Factory
{
protected string $model = User::class;
public function definition(): array
{
return [
'name' => Fake::name(),
'email' => Fake::email(),
'role' => 'viewer',
'is_active' => true,
];
}
public function admin(): static
{
return $this->state(['role' => 'admin']);
}
public function inactive(): static
{
return $this->state(['is_active' => false]);
}
public function withVerifiedEmail(): static
{
return $this->state(['email_verified_at' => date('Y-m-d H:i:s')]);
}
}Using states:
// Create an admin user
$admin = UserFactory::new()->admin()->create();
// Create 5 inactive users
$users = UserFactory::new()->inactive()->count(5)->create();
// Chain multiple states
$user = UserFactory::new()->admin()->withVerifiedEmail()->create();Using Factories in Seeders
class UserSeeder extends Seeder
{
public function run(): void
{
$this->truncate('users');
// 1 admin
UserFactory::new()->admin()->create();
// 5 editors
UserFactory::new()
->state(['role' => 'editor'])
->count(5)
->create();
// 50 regular viewers
UserFactory::new()->count(50)->create();
echo " → 56 users seeded\n";
}
}The Fake Helper
Fake is a static class with methods for generating random realistic data. No external packages needed.
Names and Contacts
Fake::firstName(); // 'Ana'
Fake::lastName(); // 'Silva'
Fake::name(); // 'Ana Silva'
Fake::email(); // 'ana123@gmail.com'
Fake::email('João Lima'); // 'jo.o.lima42@outlook.com'
Fake::phone(); // '(11) 98234-1234'Internet
Fake::username(); // 'dev1234'
Fake::password(16); // 'xK9mN!qR2pL#vZ8t'
Fake::hashedPassword(); // bcrypt hash of 'password'
Fake::hashedPassword('myPass123'); // bcrypt hash of 'myPass123'
Fake::url(); // 'https://tech42.io'
Fake::slug(); // 'lorem-ipsum-dolor'
Fake::slug('Meu Título'); // 'meu-titulo'
Fake::uuid(); // 'a3f9b1c2-...'Numbers
Fake::numberBetween(1, 100); // 42
Fake::decimal(0.5, 999.99, 2); // 127.35
Fake::boolean(); // true or false (50/50)
Fake::boolean(80); // true 80% of the timeDates
Fake::date(); // '2024-03-15'
Fake::date('-1 year', 'now'); // random date in last year
Fake::dateTime(); // '2024-03-15 14:23:01'
Fake::dateTime('-6 months', '-1 week'); // within range
Fake::timestamp(); // Unix timestamp (int)Text
Fake::word(); // 'lorem'
Fake::words(4); // 'lorem ipsum dolor sit'
Fake::sentence(); // 'Lorem ipsum dolor sit amet.'
Fake::paragraph(); // multiple sentences
Fake::text(150); // text truncated to 150 chars
Fake::title(); // 'Lorem Ipsum Dolor Sit'Address
Fake::city(); // 'São Paulo'
Fake::state(); // 'SP'
Fake::zipCode(); // '01310-100'
Fake::streetAddress(); // 'Rua Lorem, 42'Arrays and Selection
Fake::randomElement(['draft', 'published', 'archived']); // 'published'
Fake::randomElements(['a', 'b', 'c', 'd'], 2); // ['b', 'd']Complete Example
Here is a complete seeding setup for a blog application:
php celestial make:seeder DatabaseSeeder
php celestial make:seeder CategorySeeder
php celestial make:seeder UserSeeder
php celestial make:seeder PostSeeder
php celestial make:factory UserFactory
php celestial make:factory PostFactorydatabase/seeds/CategorySeeder.php
class CategorySeeder extends Seeder
{
public function run(): void
{
$this->insertBatch('categories', [
['name' => 'Technology', 'slug' => 'technology', 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s')],
['name' => 'Business', 'slug' => 'business', 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s')],
['name' => 'Science', 'slug' => 'science', 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s')],
]);
}
}database/seeds/UserSeeder.php
class UserSeeder extends Seeder
{
public function run(): void
{
$this->truncate('users');
// Always create a known admin for development login
$this->insertOrIgnore('users', [
'name' => 'Admin',
'email' => 'admin@slenix.dev',
'password' => Fake::hashedPassword('password'),
'role' => 'admin',
'is_active' => true,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
// Generate 49 fake users
$rows = [];
for ($i = 0; $i < 49; $i++) {
$rows[] = [
'name' => Fake::name(),
'email' => Fake::email(),
'password' => Fake::hashedPassword('password'),
'role' => Fake::randomElement(['editor', 'viewer']),
'is_active' => Fake::boolean(90),
'created_at' => Fake::dateTime('-1 year'),
'updated_at' => Fake::dateTime('-1 month'),
];
}
$this->insertBatch('users', $rows);
}
}database/seeds/DatabaseSeeder.php
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call([
CategorySeeder::class,
UserSeeder::class,
PostSeeder::class,
]);
}
}Run everything in one command:
php celestial migrate:fresh --seedCelestial Seed Command Reference
| Command | Description |
|---|---|
make:seeder <name> | Creates a new seeder file in database/seeds/ |
make:factory <name> | Creates a new factory file in database/factories/ |
db:seed | Runs DatabaseSeeder |
db:seed --class=<Name> | Runs a specific seeder class |
migrate:fresh --seed | Resets schema and runs all seeders |