Laravel: Laravel数据填充与模型工厂
最后更新:2026-08-26
Seeder 和 Factory 是 Laravel 的"数据制造机"——一行命令就能生成千条逼真测试数据,开发环境秒变真实场景。
1. 你将学到
- 模型工厂定义:Faker 数据生成与状态修饰
- Seeder 编写与调用链:$this->call()
- 关联数据工厂:创建用户同时创建订单和商品
- 多租户数据隔离:TenantSeeder 分租户填充
- php artisan db:seed 与 migrate:fresh --seed 工作流
2. 一个测试人员的真实故事
(1) 痛点:手工造数据比写代码还慢
Alice 每次测试 ShopMetrics 都要手动创建租户、添加商店、录入商品、下订单——造 10 条测试数据要 30 分钟。Bob 更惨,他需要测试分页功能,手动录了 100 条数据后浏览器崩溃,数据全丢了。Charlie 试图写 SQL 脚本填充数据,但关联关系太复杂(租户→商店→商品→订单→订单项),SQL 写了 500 行还漏了外键。
(2) Factory + Seeder 的解法
Laravel Factory 用 Faker 自动生成逼真数据,Seeder 编排填充顺序,关联数据一行代码搞定。
// 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) 创建工厂
php artisan make:factory ShopFactory
# Or with model
php artisan make:factory ShopFactory --model=Shop
(2) 定义工厂
// 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 核心工厂定义
// 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']),
];
}
}
输出:
// 执行成功
4. 工厂状态修饰
(1) 定义状态
// 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) 使用状态
// 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 工厂状态组合
// 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();
输出:
// 执行成功
5. 关联数据工厂
(1) 创建关联
// 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 关联数据工厂
// 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();
输出:
// 执行成功
6. Seeder 编排
(1) 创建 Seeder
php artisan make:seeder TenantSeeder
php artisan make:seeder ProductSeeder
(2) 编写 Seeder
// 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) 调用链
// database/seeders/DatabaseSeeder.php
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call([
PlanSeeder::class,
TenantSeeder::class,
ProductSeeder::class,
OrderSeeder::class,
]);
}
}
▶ 示例:ShopMetrics 完整 DatabaseSeeder
// 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();
});
}
}
输出:
// 执行成功
7. 多租户数据隔离填充
(1) 分租户填充
// 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 | 随机数量 |
▶ 示例:运行填充工作流
# 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
输出:
# 命令执行成功
8. 综合示例:ShopMetrics 完整数据填充
// ============================================
// 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]);
});
});
});
}
}
❓ 常见问题
fake('zh_CN')->name(),或在 Factory 中使用 fake()->locale('zh_CN')。但建议保持英文数据以便国际化。DB::table('products')->insert($data)。关闭模型事件:Product::withoutEvents(fn () => Product::factory()->count(1000)->create())。--force 标志时务必确认命令。$shop = Shop::factory()->create(),Laravel 自动在每个测试后回滚数据库事务,测试间互不影响。📖 小节
- Factory 定义数据生成规则,Faker 提供逼真的随机数据
- 状态修饰(state)为同一模型定义不同数据场景
- 关联工厂用 has()/for() 创建关联数据,hasProducts() 魔术方法简化写法
- Seeder 编排填充顺序,DatabaseSeeder 调用子 Seeder
- 多租户数据要分租户填充,确保 tenant_id 正确关联
- 开发用 migrate:fresh --seed,生产只用 db:seed --force
📝 作业
-
基础题(⭐):为 ShopMetrics 创建 ShopFactory 和 ProductFactory,使用 tinker 生成 3 个商店各 5 个商品,验证关联数据正确。
-
进阶题(⭐⭐):编写 TenantIsolatedSeeder,生成 5 个租户,每个租户含 2 个商店、10 个商品和 20 个订单,使用工厂关联方法(has/for)创建数据。
-
挑战题(⭐⭐⭐):实现一个带状态修饰的完整数据填充方案——为 ShopFactory 添加 active/suspended/highRevenue 三种状态,为 OrderFactory 添加 completed/cancelled/refunded 三种状态,在 Seeder 中按比例分配(80% completed、15% cancelled、5% refunded)。