Laravel: Laravel数据填充与模型工厂

最后更新:2026-08-26

Seeder 和 Factory 是 Laravel 的"数据制造机"——一行命令就能生成千条逼真测试数据,开发环境秒变真实场景。

1. 你将学到


2. 一个测试人员的真实故事

(1) 痛点:手工造数据比写代码还慢

Alice 每次测试 ShopMetrics 都要手动创建租户、添加商店、录入商品、下订单——造 10 条测试数据要 30 分钟。Bob 更惨,他需要测试分页功能,手动录了 100 条数据后浏览器崩溃,数据全丢了。Charlie 试图写 SQL 脚本填充数据,但关联关系太复杂(租户→商店→商品→订单→订单项),SQL 写了 500 行还漏了外键。

(2) Factory + Seeder 的解法

Laravel Factory 用 Faker 自动生成逼真数据,Seeder 编排填充顺序,关联数据一行代码搞定。

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) 收益

Alice 用 Factory 后,30 秒生成 1000 条测试数据(含完整关联),测试分页只需 migrate:fresh --seed


3. 模型工厂

(1) 创建工厂

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

(2) 定义工厂

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 常用方法

方法 输出示例 说明
name() "Alice Johnson" 姓名
email() "alice@example.com" 邮箱
company() "Acme Corp" 公司名
slug() "acme-corp" URL slug
paragraph() "Lorem ipsum..." 段落文本
randomFloat(2, 0, 100) 45.67 小数
numberBetween(1, 100) 42 整数范围
randomElement([...]) 取数组中随机一个 枚举值
dateTimeThisYear() "2024-06-15" 日期
imageUrl() "https://via.placeholder.com/640x480" 图片 URL
unique() 确保唯一 修饰器

▶ 示例:ShopMetrics 核心工厂定义

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']),
        ];
    }
}

输出:

TEXT 📖 仅展示
// 执行成功

4. 工厂状态修饰

(1) 定义状态

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) 使用状态

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();
用法 说明
factory()->create() 默认状态,存入 DB
factory()->make() 默认状态,不存 DB
factory()->suspended()->create() 使用 suspended 状态
factory()->count(10)->create() 创建 10 条
factory()->for(Tenant::factory()) 关联父模型

▶ 示例:ShopMetrics 工厂状态组合

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();

输出:

TEXT 📖 仅展示
// 执行成功

5. 关联数据工厂

(1) 创建关联

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();
方法 说明 示例
has() 创建子关联 has(Product::factory()->count(5))
hasProducts(5) 魔术方法简写 等价 has(Product::factory()->count(5), 'products')
for() 关联父模型 for($tenant)for(Tenant::factory())

▶ 示例:ShopMetrics 关联数据工厂

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();

输出:

TEXT 📖 仅展示
// 执行成功

6. Seeder 编排

(1) 创建 Seeder

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

(2) 编写 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) 调用链

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

▶ 示例:ShopMetrics 完整 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();
        });
    }
}

输出:

TEXT 📖 仅展示
// 执行成功

7. 多租户数据隔离填充

(1) 分租户填充

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) 数据量规划

实体 每租户 10 租户总计 说明
Users 5 50 含 1 个 owner
Shops 3 30 全部 active
Products 20/shop 600 随机 active/inactive
Orders 30/shop 900 多种状态
OrderItems 1-5/order ~2700 随机数量

▶ 示例:运行填充工作流

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

输出:

TEXT 📖 仅展示
# 命令执行成功

8. 综合示例: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]);
                    });
            });
        });
    }
}

❓ 常见问题

Q Factory 和 Seeder 有什么区别?
A Factory 定义单条数据的生成规则(像模具),Seeder 编排数据生成的顺序和数量(像生产线)。Factory 可在测试中直接使用,Seeder 通过 db:seed 命令运行。
Q make() 和 create() 有什么区别?
A make() 生成模型实例但不存入数据库,用于不需要持久化的场景;create() 生成并存入数据库,用于需要 ID 和关联查询的场景。
Q 如何生成中文测试数据?
A 修改 Faker 语言:fake('zh_CN')->name(),或在 Factory 中使用 fake()->locale('zh_CN')。但建议保持英文数据以便国际化。
Q Seeder 运行很慢怎么办?
A 减少 Factory 调用次数,改用批量插入:DB::table('products')->insert($data)。关闭模型事件:Product::withoutEvents(fn () => Product::factory()->count(1000)->create())
Q 生产环境能用 Seeder 吗?
A 可以,但要谨慎。生产环境只用 Seeder 添加初始数据(如 plans 表),不要用 Factory 生成假数据。加 --force 标志时务必确认命令。
Q 如何在测试中使用 Factory?
A 在测试方法中直接调用 Factory:$shop = Shop::factory()->create(),Laravel 自动在每个测试后回滚数据库事务,测试间互不影响。

📖 小节


📝 作业

  1. 基础题(⭐):为 ShopMetrics 创建 ShopFactory 和 ProductFactory,使用 tinker 生成 3 个商店各 5 个商品,验证关联数据正确。

  2. 进阶题(⭐⭐):编写 TenantIsolatedSeeder,生成 5 个租户,每个租户含 2 个商店、10 个商品和 20 个订单,使用工厂关联方法(has/for)创建数据。

  3. 挑战题(⭐⭐⭐):实现一个带状态修饰的完整数据填充方案——为 ShopFactory 添加 active/suspended/highRevenue 三种状态,为 OrderFactory 添加 completed/cancelled/refunded 三种状态,在 Seeder 中按比例分配(80% completed、15% cancelled、5% refunded)。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

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

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