跳转到主要内容

什么是工厂

在测试和数据库填充中,往往需要向数据库插入记录。 比起手动指定每一列的值,使用 Laravel 的模型工厂可以为每个 Eloquent 模型定义一套默认属性。 每个新的 Laravel 应用都自带了 database/factories/UserFactory.php
namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

class UserFactory extends Factory
{
    // 缓存同一个密码,避免每次都重新哈希
    protected static ?string $password;

    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => static::$password ??= Hash::make('password'),
            'remember_token' => Str::random(10),
        ];
    }
}
通过 fake() 辅助函数可以使用 Faker 库,生成各种随机数据。
Faker 的语言环境可以通过 config/app.php 中的 faker_locale 选项修改。

生成工厂类

使用 make:factory Artisan 命令来创建工厂。
php artisan make:factory PostFactory
新的工厂类会被放到 database/factories 目录下。

模型与工厂的自动发现

在模型上使用 HasFactory trait 时,Laravel 会自动查找对应的工厂类。 它会在 Database\Factories 命名空间中查找 “模型名 + Factory” 的类名。 如果命名规则不匹配,可以使用 UseFactory 属性显式指定。
use Illuminate\Database\Eloquent\Attributes\UseFactory;
use Database\Factories\Administration\FlightFactory;

#[UseFactory(FlightFactory::class)]
class Flight extends Model
{
    // ...
}

定义工厂

definition() 方法

工厂的核心是 definition() 方法,用来定义默认属性。
namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

class PostFactory extends Factory
{
    public function definition(): array
    {
        return [
            'user_id' => \App\Models\User::factory(),
            'title' => fake()->sentence(),
            'content' => fake()->paragraphs(3, true),
            'published_at' => fake()->optional()->dateTimeBetween('-1 year', 'now'),
        ];
    }
}
Faker 常用方法一览:
方法示例结果
fake()->name()John Doe
fake()->email()[email protected]
fake()->sentence()随机句子
fake()->paragraph()随机段落
fake()->numberBetween(1, 100)1100 的整数
fake()->dateTime()随机日期时间
fake()->boolean()true / false

工厂状态(States)

通过 state 方法可以为工厂定义单独的属性变化。
use Illuminate\Database\Eloquent\Factories\Factory;

class UserFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'account_status' => 'active',
        ];
    }

    // 停用用户的 state
    public function suspended(): static
    {
        return $this->state(fn (array $attributes) => [
            'account_status' => 'suspended',
        ]);
    }

    // 管理员用户的 state
    public function admin(): static
    {
        return $this->state(fn (array $attributes) => [
            'is_admin' => true,
        ]);
    }
}
state 可以组合使用。
$user = User::factory()->suspended()->create();
$admin = User::factory()->admin()->create();

软删除 state

对启用了软删除的模型,可以使用内置的 trashed() state。
$user = User::factory()->trashed()->create();

工厂回调(Callbacks)

通过 afterMakingafterCreating 可以定义模型生成后的附加处理。
namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

class UserFactory extends Factory
{
    public function configure(): static
    {
        return $this->afterMaking(function (User $user) {
            // make() 之后执行(尚未存入数据库)
        })->afterCreating(function (User $user) {
            // create() 之后执行(已经存入数据库)
        });
    }

    // ...
}
state 方法内也可以注册回调。
public function withProfile(): static
{
    return $this->state(fn (array $attributes) => [])
        ->afterCreating(function (User $user) {
            $user->profile()->create([
                'bio' => fake()->paragraph(),
            ]);
        });
}

生成模型

make() — 不保存到数据库

use App\Models\User;

// 生成 1 个
$user = User::factory()->make();

// 生成 3 个(返回集合)
$users = User::factory()->count(3)->make();

create() — 保存到数据库

// 生成 1 个并保存
$user = User::factory()->create();

// 生成 3 个并保存
$users = User::factory()->count(3)->create();

覆盖属性

make()create() 传入数组,可以只覆盖特定属性。
$user = User::factory()->make([
    'name' => '山田太郎',
]);

$user = User::factory()->create([
    'name' => '铃木花子',
    'email' => '[email protected]',
]);
也可以通过 state 实现内联覆盖。
$user = User::factory()->state([
    'name' => '田中一郎',
])->make();
用工厂生成模型时,批量赋值保护会被自动禁用。

Sequence(序列)

在批量生成模型时,如果希望按顺序切换属性,可以使用 Sequence
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Sequence;

$users = User::factory()
    ->count(10)
    ->state(new Sequence(
        ['admin' => 'Y'],
        ['admin' => 'N'],
    ))
    ->create();
// 5 条数据 admin='Y',5 条 admin='N'
使用 sequence() 方法可以写得更简洁。
$users = User::factory()
    ->count(2)
    ->sequence(
        ['name' => '第一个用户'],
        ['name' => '第二个用户'],
    )
    ->create();
也可以通过闭包动态生成随机值。
use Illuminate\Database\Eloquent\Factories\Sequence;

$users = User::factory()
    ->count(10)
    ->state(new Sequence(
        fn (Sequence $sequence) => ['name' => '用户 ' . $sequence->index],
    ))
    ->create();

关联关系的工厂

Has Many(一对多)

使用 has() 方法生成带“一对多”关联的模型。
use App\Models\Post;
use App\Models\User;

// 生成一个 User,附带 3 篇 Post
$user = User::factory()
    ->has(Post::factory()->count(3))
    ->create();
使用魔术方法(has + 关联名的复数)可以写得更简洁。
$user = User::factory()
    ->hasPosts(3)
    ->create();

// 覆盖属性
$user = User::factory()
    ->hasPosts(3, ['published' => false])
    ->create();

Belongs To(反向关联)

使用 for() 方法指定所“属于”的父模型。
use App\Models\Post;
use App\Models\User;

// 生成属于特定用户的 3 篇 Post
$posts = Post::factory()
    ->count(3)
    ->for(User::factory()->state(['name' => '田中花子']))
    ->create();

// 也可以使用已存在的模型作为父级
$user = User::factory()->create();
$posts = Post::factory()->count(3)->for($user)->create();
魔术方法版本:
$posts = Post::factory()
    ->count(3)
    ->forUser(['name' => '田中花子'])
    ->create();

Many to Many(多对多)

使用 hasAttached() 方法处理多对多关联,还可以指定中间表的属性。
use App\Models\Role;
use App\Models\User;

$user = User::factory()
    ->hasAttached(
        Role::factory()->count(3),
        ['active' => true]  // 中间表属性
    )
    ->create();
魔术方法版本:
$user = User::factory()
    ->hasRoles(1, ['name' => 'Editor'])
    ->create();

多态关联

普通“一对多”一样,可以使用 has() 或魔术方法。
use App\Models\Post;

$post = Post::factory()->hasComments(3)->create();
对于 morphTo 关联,不能使用魔术方法。需要在 for() 中明确指定关联名。
$comments = Comment::factory()->count(3)->for(
    Post::factory(), 'commentable'
)->create();

工厂内定义关联

definition() 中把另一个工厂赋值给外键,就能在生成模型时自动创建父模型。
class PostFactory extends Factory
{
    public function definition(): array
    {
        return [
            'user_id' => \App\Models\User::factory(),
            'title' => fake()->sentence(),
            'content' => fake()->paragraph(),
        ];
    }
}

recycle() — 复用已有模型

如果多个关联想复用同一个模型实例,可以使用 recycle()
// 同一个 Airline 在 Ticket 与 Flight 中反复使用
Ticket::factory()
    ->recycle(Airline::factory()->create())
    ->create();

// 从集合中随机挑选
$airlines = Airline::factory()->count(3)->create();
Ticket::factory()->count(10)->recycle($airlines)->create();

在数据填充中使用

可以在 DatabaseSeeder 或其他 Seeder 类中调用工厂。
namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        // 生成 10 个用户,每个用户各带 3 篇 Post
        User::factory()
            ->count(10)
            ->hasPosts(3)
            ->create();
    }
}
执行 Seeder:
php artisan db:seed

在测试中使用

测试时通常将 RefreshDatabase trait 与工厂搭配使用。
namespace Tests\Feature;

use App\Models\User;
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PostTest extends TestCase
{
    use RefreshDatabase;

    public function test_user_can_view_their_posts(): void
    {
        $user = User::factory()->create();
        $posts = Post::factory()->count(3)->for($user)->create();

        $response = $this->actingAs($user)
            ->get('/dashboard');

        $response->assertOk();
        $response->assertSee($posts->first()->title);
    }

    public function test_suspended_user_cannot_post(): void
    {
        $user = User::factory()->suspended()->create();

        $response = $this->actingAs($user)
            ->post('/posts', ['title' => '测试']);

        $response->assertForbidden();
    }
}
RefreshDatabase 会在每个测试之间重置数据库,因此测试之间的数据不会相互干扰。
使用 Pest 编写测试时工厂 API 完全一样。 记得通过 uses(RefreshDatabase::class) 重置数据库。

下一步

数据库填充

查看如何组合 Seeder 与工厂来注入初始数据。

测试入门

了解 Laravel 的测试功能以及 RefreshDatabase trait 的使用方式。
最后修改于 2026年7月13日