404 Not Found

404 Not Found


nginx

Laravel Data Seeding and Model Factories

Seeder and Factory are Laravel's "data generators"—with just one command, you can generate thousands of realistic test data records, instantly transforming your development environment into a real-world scenario.

1. What You'll Learn


2. A True Story from a Tester

(1) Pain Point: Manually generating data is even slower than writing code

Every time Alice tests ShopMetrics, she has to manually create tenants, add stores, enter products, and place orders—it takes 30 minutes to generate 10 test records. Bob has it even worse; he needed to test the pagination feature, but after manually entering 100 records, his browser crashed and he lost all the data. Charlie tried writing an SQL script to populate the data, but the relationships were too complex (tenant → store → product → order → order line). He wrote 500 lines of SQL and still missed a foreign key.

(2) The Factory + Seeder Approach

Laravel Factories use Faker to automatically generate realistic data; Seeders orchestrate the population order, and relating data is done with just one line of code.

PHP
// Factory generates realistic data
$shop = Shop::factory()->create();
$shop->products()->createMany(
    Product::factory()->count(10)->make()->toArray()
);
// 1 shop + 10 products in 2 lines

(3) Revenue

After using Factory, Alice generated 1,000 test records (including all relationships) in 30 seconds, and testing pagination took just migrate:fresh --seed.


3. Model Factory

(1) Create a Factory

BASH
php artisan make:factory ShopFactory
# Or with model
php artisan make:factory ShopFactory --model=Shop

(2) Defining a Factory

PHP
// database/factories/ShopFactory.php
class ShopFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->company(),
            'slug' => fake()->unique()->slug(),
            'description' => fake()->paragraph(),
            'status' => fake()->randomElement(['active', 'suspended', 'closed']),
            'revenue' => fake()->randomFloat(2, 100, 100000),
        ];
    }
}

(3) Faker's Common Strategies

Method Sample Output Description
name() "Alice Johnson" Name
email() "alice@example.com" Email
company() "Acme Corp" Company Name
slug() "acme-corp" URL slug
paragraph() "Lorem ipsum..." Paragraph text
randomFloat(2, 0, 100) 45.67 Decimal
numberBetween(1, 100) 42 Integer range
randomElement([...]) Select a random element from an array Enumerate values
dateTimeThisYear() "June 15, 2024" Date
imageUrl() "https://via.placeholder.com/640x480" Image URL
unique() Ensure Uniqueness Modifier

(1) ▶ Example: ShopMetrics Core Factory Definition

PHP
// database/factories/TenantFactory.php
class TenantFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->company(),
            'slug' => fake()->unique()->slug(),
            'domain' => fake()->unique()->domainName(),
            'status' => 'active',
        ];
    }
}

// database/factories/ProductFactory.php
class ProductFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->words(3, true),
            'sku' => strtoupper(fake()->unique()->lexify('???-????')),
            'price' => fake()->randomFloat(2, 9.99, 999.99),
            'stock' => fake()->numberBetween(0, 500),
            'is_active' => true,
        ];
    }
}

// database/factories/OrderFactory.php
class OrderFactory extends Factory
{
    public function definition(): array
    {
        return [
            'order_number' => fake()->unique()->numerify('ORD-########'),
            'subtotal' => fake()->randomFloat(2, 10, 5000),
            'discount' => 0,
            'total' => fake()->randomFloat(2, 10, 5000),
            'status' => fake()->randomElement(['pending', 'processing', 'completed', 'cancelled']),
        ];
    }
}

Output:

TEXT
// Execution Successful

4. Factory State Modifiers

(1) Defining States

PHP
// database/factories/ShopFactory.php
class ShopFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->company(),
            'slug' => fake()->unique()->slug(),
            'status' => 'active',
            'revenue' => fake()->randomFloat(2, 100, 50000),
        ];
    }

    public function suspended(): static
    {
        return $this->state(fn (array $attributes) => [
            'status' => 'suspended',
            'revenue' => 0,
        ]);
    }

    public function highRevenue(): static
    {
        return $this->state(fn (array $attributes) => [
            'revenue' => fake()->randomFloat(2, 50000, 500000),
        ]);
    }
}

(2) Usage Status

PHP
// Active shop (default)
$shop = Shop::factory()->create();

// Suspended shop
$shop = Shop::factory()->suspended()->create();

// High revenue shop
$shop = Shop::factory()->highRevenue()->create();

// Combine states
$shop = Shop::factory()->highRevenue()->suspended()->create();
Usage Description
factory()->create() Default state, stored in the DB
factory()->make() Default state, not stored in the DB
factory()->suspended()->create() Using the "suspended" status
factory()->count(10)->create() Created 10 entries
factory()->for(Tenant::factory()) Associate with Parent Model

(1) ▶ Example: ShopMetrics Factory Status Combinations

PHP
// database/factories/OrderFactory.php
class OrderFactory extends Factory
{
    public function definition(): array
    {
        return [
            'order_number' => fake()->unique()->numerify('ORD-########'),
            'subtotal' => fake()->randomFloat(2, 10, 5000),
            'discount' => 0,
            'total' => fake()->randomFloat(2, 10, 5000),
            'status' => 'pending',
        ];
    }

    public function completed(): static
    {
        return $this->state(fn (array $attributes) => [
            'status' => 'completed',
        ]);
    }

    public function cancelled(): static
    {
        return $this->state(fn (array $attributes) => [
            'status' => 'cancelled',
            'total' => 0,
            'subtotal' => 0,
        ]);
    }

    public function highValue(): static
    {
        return $this->state(fn (array $attributes) => [
            'subtotal' => fake()->randomFloat(2, 1000, 10000),
            'total' => fake()->randomFloat(2, 1000, 10000),
        ]);
    }
}

// Usage
$order = Order::factory()->completed()->highValue()->create();

Output:

TEXT
// Execution Successful

5. Linked Data Factory

(1) Create a relationship

PHP
// Create shop with products
$shop = Shop::factory()
    ->has(Product::factory()->count(10), 'products')
    ->create();

// Or using magic method
$shop = Shop::factory()
    ->hasProducts(10)
    ->create();

// Create order for a specific shop
$order = Order::factory()
    ->for($shop)
    ->create();

// Create user with tenant
$user = User::factory()
    ->for(Tenant::factory())
    ->create();
Method Description Example
has() Create Sub-Association has(Product::factory()->count(5))
hasProducts(5) Shorthand for magic methods Equivalent to has(Product::factory()->count(5), 'products')
for() Associated Parent Model for($tenant) or for(Tenant::factory())

(1) ▶ Example: ShopMetrics Data Integration Factory

PHP
// Create a complete tenant ecosystem
$tenant = Tenant::factory()
    ->has(User::factory()->count(3))
    ->has(Shop::factory()->count(5)->hasProducts(10))
    ->create();

// Create an order with items for a shop
$shop = Shop::factory()->create();
$products = Product::factory()->count(5)->for($shop)->create();

$order = Order::factory()
    ->for($shop->tenant)
    ->for($shop)
    ->for(User::factory()->for($shop->tenant))
    ->has(OrderItem::factory()->count(3)->state([
        'product_id' => $products->random()->id,
    ]), 'items')
    ->create();

Output:

TEXT
// Execution Successful

6. Seeder Orchestration

(1) Create a Seeder

BASH
php artisan make:seeder TenantSeeder
php artisan make:seeder ProductSeeder

(2) Writing a Seeder

PHP
// database/seeders/TenantSeeder.php
class TenantSeeder extends Seeder
{
    public function run(): void
    {
        Tenant::factory()
            ->count(10)
            ->has(User::factory()->count(3))
            ->has(Shop::factory()->count(5)->hasProducts(10))
            ->create();
    }
}

(3) Call Chain

PHP
// database/seeders/DatabaseSeeder.php
class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            PlanSeeder::class,
            TenantSeeder::class,
            ProductSeeder::class,
            OrderSeeder::class,
        ]);
    }
}

(1) ▶ Example: ShopMetrics Complete DatabaseSeeder

PHP
// database/seeders/DatabaseSeeder.php
class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        // 1. Create plans
        Plan::factory()->createMany([
            ['name' => 'Starter', 'slug' => 'starter', 'price' => 29, 'shop_limit' => 5],
            ['name' => 'Pro', 'slug' => 'pro', 'price' => 79, 'shop_limit' => 25],
            ['name' => 'Enterprise', 'slug' => 'enterprise', 'price' => 199, 'shop_limit' => null],
        ]);

        // 2. Create tenants with full data
        Tenant::factory()
            ->count(20)
            ->has(User::factory()->count(5))
            ->has(Shop::factory()->count(3)->hasProducts(15))
            ->create();

        // 3. Create orders for each shop
        Shop::all()->each(function ($shop) {
            Order::factory()
                ->count(50)
                ->for($shop->tenant)
                ->for($shop)
                ->for($shop->tenant->users->random())
                ->create();
        });
    }
}

Output:

TEXT
// Execution Successful

7. Multi-tenant Data Isolation Population

(1) Subtenant Allocation

PHP
// database/seeders/TenantIsolatedSeeder.php
class TenantIsolatedSeeder extends Seeder
{
    public function run(): void
    {
        $tenants = Tenant::factory()->count(10)->create();

        foreach ($tenants as $tenant) {
            // Each tenant gets isolated data
            $users = User::factory()->count(5)->for($tenant)->create();
            $shops = Shop::factory()->count(3)->for($tenant)->create();

            foreach ($shops as $shop) {
                $products = Product::factory()->count(20)->for($shop)->create();

                // Create orders with items
                Order::factory()->count(30)->for($tenant)->for($shop)
                    ->for($users->random())
                    ->create()
                    ->each(function ($order) use ($products) {
                        $orderItems = $products->random(rand(1, 5));
                        foreach ($orderItems as $product) {
                            $order->items()->create([
                                'product_id' => $product->id,
                                'quantity' => rand(1, 3),
                                'price' => $product->price,
                            ]);
                        }
                        $order->update([
                            'subtotal' => $order->items->sum(fn ($i) => $i->price * $i->quantity),
                            'total' => $order->items->sum(fn ($i) => $i->price * $i->quantity),
                        ]);
                    });
            }
        }
    }
}

(2) Data Volume Planning

Entity Per Tenant Total for 10 Tenants Notes
Users 5 50 including 1 owner
Shops 3 30 All active
Products 20/shop 600 Random active/inactive
Orders 30/shop 900 Various statuses
OrderItems 1-5 per order ~2,700 Random quantity

(1) ▶ Example: Running a Populate Workflow

BASH
# Fresh migrate + seed (development)
php artisan migrate:fresh --seed

# Run specific seeder
php artisan db:seed --class=TenantSeeder

# Run with custom amount
php artisan db:seed --class=OrderSeeder

# Production — never use migrate:fresh!
php artisan migrate --force
php artisan db:seed --force

Output:

TEXT
# Command executed successfully

8. Comprehensive Example: Complete Data Population for ShopMetrics

PHP
// ============================================
// Comprehensive: ShopMetrics Complete Seeder
// Covers: factories, states, relationships, tenant isolation
// ============================================

// database/seeders/ShopMetricsSeeder.php
class ShopMetricsSeeder extends Seeder
{
    public function run(): void
    {
        // Step 1: Plans
        $starter = Plan::create(['name' => 'Starter', 'slug' => 'starter', 'price' => 29, 'shop_limit' => 5, 'features' => ['basic_analytics']]);
        $pro = Plan::create(['name' => 'Pro', 'slug' => 'pro', 'price' => 79, 'shop_limit' => 25, 'features' => ['advanced_analytics', 'api_access']]);
        $enterprise = Plan::create(['name' => 'Enterprise', 'slug' => 'enterprise', 'price' => 199, 'shop_limit' => null, 'features' => ['custom_analytics', 'api_access', 'sso']]);

        // Step 2: Create super admin
        User::factory()->create(['email' => 'admin@shopmetrics.io', 'role' => 'super_admin']);

        // Step 3: Create tenants with isolated data
        Tenant::factory()->count(20)->create()->each(function ($tenant) use ($starter, $pro, $enterprise) {
            // Assign random plan
            $plan = fake()->randomElement([$starter, $pro, $enterprise]);
            Subscription::create(['tenant_id' => $tenant->id, 'plan_id' => $plan->id, 'status' => 'active']);

            // Create tenant owner
            $owner = User::factory()->for($tenant)->create(['role' => 'tenant_owner']);

            // Create analysts
            $analysts = User::factory()->count(3)->for($tenant)->create(['role' => 'analyst']);

            // Create shops with products
            $shopCount = min(fake()->numberBetween(2, 5), $plan->shop_limit ?? 99);
            Shop::factory()->count($shopCount)->for($tenant)->create()->each(function ($shop) use ($tenant, $owner, $analysts) {
                // Products per shop
                Product::factory()->count(fake()->numberBetween(10, 30))->for($shop)->create();

                // Orders per shop
                Order::factory()->count(fake()->numberBetween(20, 80))
                    ->for($tenant)
                    ->for($shop)
                    ->for(fake()->randomElement(array_merge([$owner], $analysts->all())))
                    ->create()
                    ->each(function ($order) use ($shop) {
                        $products = $shop->products()->inRandomOrder()->take(rand(1, 5))->get();
                        foreach ($products as $product) {
                            $qty = rand(1, 3);
                            $order->items()->create([
                                'product_id' => $product->id,
                                'quantity' => $qty,
                                'price' => $product->price,
                            ]);
                        }
                        $subtotal = $order->items->sum(fn ($i) => $i->price * $i->quantity);
                        $order->update(['subtotal' => $subtotal, 'total' => $subtotal]);
                    });
            });
        });
    }
}

❓ FAQ

Q What is the difference between a Factory and a Seeder?
A A Factory defines the rules for generating individual data records (like a mold), while a Seeder orchestrates the order and quantity of data generation (like an assembly line). A Factory can be used directly in tests, while a Seeder is run using the db:seed command.
Q What is the difference between make() and create()?
A make() creates a model instance but does not save it to the database; it is used in scenarios where persistence is not required. create() creates the model instance and saves it to the database; it is used in scenarios where an ID and join queries are required.
Q How do I generate Chinese test data?
A Change the Faker language to fake('zh_CN')->name(), or use fake()->locale('zh_CN') in Factory. However, we recommend keeping the data in English for internationalization purposes.
Q What should I do if Seeder is running slowly?
A Reduce the number of Factory calls and switch to batch inserts: DB::table('products')->insert($data). Disable model events: Product::withoutEvents(fn () => Product::factory()->count(1000)->create()).
Q Can Seeder be used in a production environment?
A Yes, but with caution. In a production environment, use Seeder only to add initial data (such as to the plans table); do not use Factory to generate dummy data. Be sure to verify the command when adding the --force flag.
Q How do I use Factory in tests?
A Call Factory directly within your test method: $shop = Shop::factory()->create(). Laravel automatically rolls back the database transaction after each test, so tests do not affect one another.

📖 Summary


📝 Exercises

  1. Basic Exercise (⭐): Create a ShopFactory and a ProductFactory for ShopMetrics, use Tinker to generate 3 stores with 5 products each, and verify that the associated data is correct.

  2. Advanced Exercise (⭐⭐): Write a TenantIsolatedSeeder that generates 5 tenants, each containing 2 stores, 10 products, and 20 orders, using factory association methods (has/for) to create the data.

  3. Challenge (⭐⭐⭐): Implement a complete data seeding solution that takes state into account—add three states (active, suspended, and highRevenue) to ShopFactory, and three states (completed, canceled, and refunded) to OrderFactory, and distribute them proportionally in the Seeder (80% completed, 15% canceled, 5% refunded) .

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏