跳至内容

Eloquent:Factories

介绍

在测试应用程序或填充数据库时,您可能需要向数据库中插入一些记录。Laravel 允许您使用模型Factories为每个Eloquent 模型定义一组默认属性,而无需手动指定每列的值。

要查看如何编写Factories的示例,请查看database/factories/UserFactory.php应用程序中的文件。此Factories包含在所有新的 Laravel 应用程序中,并包含以下Factories定义:

1namespace Database\Factories;
2 
3use Illuminate\Database\Eloquent\Factories\Factory;
4use Illuminate\Support\Facades\Hash;
5use Illuminate\Support\Str;
6 
7/**
8 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
9 */
10class UserFactory extends Factory
11{
12 /**
13 * The current password being used by the factory.
14 */
15 protected static ?string $password;
16 
17 /**
18 * Define the model's default state.
19 *
20 * @return array<string, mixed>
21 */
22 public function definition(): array
23 {
24 return [
25 'name' => fake()->name(),
26 'email' => fake()->unique()->safeEmail(),
27 'email_verified_at' => now(),
28 'password' => static::$password ??= Hash::make('password'),
29 'remember_token' => Str::random(10),
30 ];
31 }
32 
33 /**
34 * Indicate that the model's email address should be unverified.
35 */
36 public function unverified(): static
37 {
38 return $this->state(fn (array $attributes) => [
39 'email_verified_at' => null,
40 ]);
41 }
42}

如你所见,Factories最基本的形式是继承 Laravel 基Factories类并定义一个definition方法。该definition方法返回一组默认的属性值,这些属性值在使用Factories创建模型时应该被应用。

通过fakeHelpers,Factories可以访问Faker PHP 库,这使您可以方便地生成各种随机数据以进行测试和Seeding。

faker_locale您可以通过更新配置文件中的选项 来更改应用程序的 Faker 语言环境config/app.php

定义模型Factories

发电Factories

要创建Factories,请执行make:factory Artisan 命令

1php artisan make:factory PostFactory

新的Factories类将被放置在您的database/factories目录中。

模型和Factories发现约定

一旦定义了Factories,您就可以使用特征factory为模型提供的静态方法Illuminate\Database\Eloquent\Factories\HasFactory来实例化该模型的Factories实例。

特征HasFactoryfactory方法将使用约定来确定特征所分配到的模型的正确Factories。具体来说,该方法将在Database\Factories命名空间中查找类名与模型名称匹配且以 为后缀的FactoriesFactory。如果这些约定不适用于您的特定应用程序或Factories,您可以覆盖newFactory模型上的方法,以直接返回模型对应Factories的实例:

1use Database\Factories\Administration\FlightFactory;
2 
3/**
4 * Create a new factory instance for the model.
5 */
6protected static function newFactory()
7{
8 return FlightFactory::new();
9}

然后,model在相应的Factories上定义一个属性:

1use App\Administration\Flight;
2use Illuminate\Database\Eloquent\Factories\Factory;
3 
4class FlightFactory extends Factory
5{
6 /**
7 * The name of the factory's corresponding model.
8 *
9 * @var class-string<\Illuminate\Database\Eloquent\Model>
10 */
11 protected $model = Flight::class;
12}

Factories状态

状态操作方法允许您定义可任意组合应用于模型Factories的离散修改。例如,您的Database\Factories\UserFactoryFactories可能包含一个suspended用于修改其某个默认属性值的状态方法。

状态转换方法通常调用stateLaravel 基Factories类提供的方法。该state方法接受一个闭包,该闭包接收Factories定义的原始属性数组,并返回一个需要修改的属性数组:

1use Illuminate\Database\Eloquent\Factories\Factory;
2 
3/**
4 * Indicate that the user is suspended.
5 */
6public function suspended(): Factory
7{
8 return $this->state(function (array $attributes) {
9 return [
10 'account_status' => 'suspended',
11 ];
12 });
13}

“垃圾”状态

如果你的 Eloquent 模型可以被软删除,你可以调用内置的trashedstate 方法来表明创建的模型已经被“软删除”。你无需手动定义状态,trashed因为它会自动对所有Factories生效:

1use App\Models\User;
2 
3$user = User::factory()->trashed()->create();

Factories回调

afterMakingFactories回调使用和方法注册afterCreating,并允许您在创建模型后执行其他任务。您应该通过configure在Factories类中定义方法来注册这些回调。当Factories实例化时,Laravel 会自动调用此方法:

1namespace Database\Factories;
2 
3use App\Models\User;
4use Illuminate\Database\Eloquent\Factories\Factory;
5 
6class UserFactory extends Factory
7{
8 /**
9 * Configure the model factory.
10 */
11 public function configure(): static
12 {
13 return $this->afterMaking(function (User $user) {
14 // ...
15 })->afterCreating(function (User $user) {
16 // ...
17 });
18 }
19 
20 // ...
21}

您还可以在状态方法中注册Factories回调来执行特定于给定状态的附加任务:

1use App\Models\User;
2use Illuminate\Database\Eloquent\Factories\Factory;
3 
4/**
5 * Indicate that the user is suspended.
6 */
7public function suspended(): Factory
8{
9 return $this->state(function (array $attributes) {
10 return [
11 'account_status' => 'suspended',
12 ];
13 })->afterMaking(function (User $user) {
14 // ...
15 })->afterCreating(function (User $user) {
16 // ...
17 });
18}

使用Factories创建模型

实例化模型

定义好Factories后,你可以使用特征factory提供给模型的静态方法Illuminate\Database\Eloquent\Factories\HasFactory来实例化该模型的Factories实例。让我们看几个创建模型的示例。首先,我们将使用该make方法创建模型,但不将它们持久化到数据库:

1use App\Models\User;
2 
3$user = User::factory()->make();

您可以使用下列方法创建多个模型的集合count

1$users = User::factory()->count(3)->make();

申请州

你也可以将任意状态应用到模型上。如果你想对模型应用多种状态转换,可以直接调用状态转换方法:

1$users = User::factory()->count(5)->suspended()->make();

覆盖属性

如果你想覆盖模型的某些默认值,可以向该make方法传递一个值数组。只有指定的属性会被替换,其余属性仍保留Factories指定的默认值:

1$user = User::factory()->make([
2 'name' => 'Abigail Otwell',
3]);

或者,state可以直接在Factories实例上调用该方法来执行内联状态转换:

1$user = User::factory()->state([
2 'name' => 'Abigail Otwell',
3])->make();

使用Factories创建模型时,批量分配保护会自动禁用。

持久化模型

create方法实例化模型实例并使用 Eloquent 的方法将其持久化到数据库中save

1use App\Models\User;
2 
3// Create a single App\Models\User instance...
4$user = User::factory()->create();
5 
6// Create three App\Models\User instances...
7$users = User::factory()->count(3)->create();

您可以通过将属性数组传递给create方法来覆盖Factories的默认模型属性:

1$user = User::factory()->create([
2 'name' => 'Abigail',
3]);

序列

有时你可能希望在每次创建模型时交替更改给定模型属性的值。你可以通过将状态转换定义为序列来实现这一点。例如,你可能希望在每次创建用户时交替更改admin之间的列值YN

1use App\Models\User;
2use Illuminate\Database\Eloquent\Factories\Sequence;
3 
4$users = User::factory()
5 ->count(10)
6 ->state(new Sequence(
7 ['admin' => 'Y'],
8 ['admin' => 'N'],
9 ))
10 ->create();

在此示例中,将创建五个用户,其admin值为Y,并将创建五个用户,其admin值为N

如果需要,你可以将闭包作为序列值包含进来。每次序列需要新值时,都会调用该闭包:

1use Illuminate\Database\Eloquent\Factories\Sequence;
2 
3$users = User::factory()
4 ->count(10)
5 ->state(new Sequence(
6 fn (Sequence $sequence) => ['role' => UserRoles::all()->random()],
7 ))
8 ->create();

在序列闭包中,你可以访问注入到闭包中的序列实例的$index或属性。属性包含迄今为止序列已发生的迭代次数,而属性包含序列将被调用的总次数:$count$index$count

1$users = User::factory()
2 ->count(10)
3 ->sequence(fn (Sequence $sequence) => ['name' => 'Name '.$sequence->index])
4 ->create();

为了方便起见,也可以使用sequence方法来应用序列,该方法只是在内部调用该state方法。该sequence方法接受一个闭包或序列属性的数组作为参数:

1$users = User::factory()
2 ->count(2)
3 ->sequence(
4 ['name' => 'First User'],
5 ['name' => 'Second User'],
6 )
7 ->create();

Factories关系

有很多关系

接下来,让我们探索如何使用 Laravel 的流式Factories方法构建 Eloquent 模型关系。首先,假设我们的应用程序有一个App\Models\User模型和一个App\Models\Post模型。此外,假设该User模型定义了hasMany与的关系。我们可以使用Laravel Factories提供的方法Post创建一个拥有三篇帖子的用户。该方法接受一个Factories实例:hashas

1use App\Models\Post;
2use App\Models\User;
3 
4$user = User::factory()
5 ->has(Post::factory()->count(3))
6 ->create();

按照惯例,当将Post模型传递给has方法时,Laravel 会假定该User模型必须具有posts定义关系的方法。如有必要,您可以明确指定要操作的关系的名称:

1$user = User::factory()
2 ->has(Post::factory()->count(3), 'posts')
3 ->create();

当然,你可以对相关模型进行状态操作。此外,如果你的状态更改需要访问父模型,你可以传递基于闭包的状态转换:

1$user = User::factory()
2 ->has(
3 Post::factory()
4 ->count(3)
5 ->state(function (array $attributes, User $user) {
6 return ['user_type' => $user->type];
7 })
8 )
9 ->create();

使用魔法方法

为了方便起见,你可以使用 Laravel 的魔法Factories关联方法来建立关联。例如,以下示例将使用约定来确定关联模型应通过模型posts上的关系方法创建User

1$user = User::factory()
2 ->hasPosts(3)
3 ->create();

当使用魔术方法创建Factories关系时,您可以传递一个属性数组来覆盖相关模型:

1$user = User::factory()
2 ->hasPosts(3, [
3 'published' => false,
4 ])
5 ->create();

如果您的状态更改需要访问父模型,您可以提供基于闭包的状态转换:

1$user = User::factory()
2 ->hasPosts(3, function (array $attributes, User $user) {
3 return ['user_type' => $user->type];
4 })
5 ->create();

属于关系

我们已经探索了如何使用Factories方法构建“有许多”关系,现在让我们探索一下这种关系的逆向。该for方法可用于定义Factories方法创建的模型所属的父模型。例如,我们可以创建App\Models\Post属于同一用户的三个模型实例:

1use App\Models\Post;
2use App\Models\User;
3 
4$posts = Post::factory()
5 ->count(3)
6 ->for(User::factory()->state([
7 'name' => 'Jessica Archer',
8 ]))
9 ->create();

如果您已经有一个应该与您正在创建的模型相关联的父模型实例,则可以将模型实例传递给该for方法:

1$user = User::factory()->create();
2 
3$posts = Post::factory()
4 ->count(3)
5 ->for($user)
6 ->create();

使用魔法方法

为了方便起见,你可以使用 Laravel 的魔法Factories关联方法来定义“属于”关联。例如,以下示例将使用约定来确定这三篇帖子应该属于模型user上的关系Post

1$posts = Post::factory()
2 ->count(3)
3 ->forUser([
4 'name' => 'Jessica Archer',
5 ])
6 ->create();

多对多关系

就像有许多关系一样,可以使用下列方法创建“多对多”关系has

1use App\Models\Role;
2use App\Models\User;
3 
4$user = User::factory()
5 ->has(Role::factory()->count(3))
6 ->create();

数据透视表属性

如果需要定义在连接模型的中间表/数据透视表上设置的属性,可以使用该hasAttached方法。该方法接受一个包含数据透视表属性名称和值的数组作为其第二个参数:

1use App\Models\Role;
2use App\Models\User;
3 
4$user = User::factory()
5 ->hasAttached(
6 Role::factory()->count(3),
7 ['active' => true]
8 )
9 ->create();

如果您的状态变化需要访问相关模型,您可以提供基于闭包的状态转换:

1$user = User::factory()
2 ->hasAttached(
3 Role::factory()
4 ->count(3)
5 ->state(function (array $attributes, User $user) {
6 return ['name' => $user->name.' Role'];
7 }),
8 ['active' => true]
9 )
10 ->create();

如果您已有模型实例并希望将其附加到正在创建的模型中,则可以将模型实例传递给该hasAttached方法。在此示例中,相同的三个角色将附加到所有三个用户:

1$roles = Role::factory()->count(3)->create();
2 
3$user = User::factory()
4 ->count(3)
5 ->hasAttached($roles, ['active' => true])
6 ->create();

使用魔法方法

为了方便起见,你可以使用 Laravel 的魔法Factories关系方法来定义多对多关系。例如,以下示例将使用约定来确定应通过模型roles上的关系方法来创建相关模型User

1$user = User::factory()
2 ->hasRoles(1, [
3 'name' => 'Editor'
4 ])
5 ->create();

多态关系

多态关系也可以使用Factories函数创建。多态“多态”关系的创建方式与典型的“包含多个”关系相同。例如,假设一个App\Models\Post模型morphMany与另一个App\Models\Comment模型之间存在关系:

1use App\Models\Post;
2 
3$post = Post::factory()->hasComments(3)->create();

变形为关系

魔法方法不能用于创建morphTo关系。相反,for必须直接使用该方法,并且必须显式提供关系的名称。例如,假设Comment模型中有一个commentable定义morphTo关系的方法。在这种情况下,我们可以直接使用该方法创建属于同一篇帖子的三条评论for

1$comments = Comment::factory()->count(3)->for(
2 Post::factory(), 'commentable'
3)->create();

多态多对多关系

多态“多对多”(morphToMany/ morphedByMany)关系可以像非多态“多对多”关系一样创建:

1use App\Models\Tag;
2use App\Models\Video;
3 
4$videos = Video::factory()
5 ->hasAttached(
6 Tag::factory()->count(3),
7 ['public' => true]
8 )
9 ->create();

当然,魔术has方法也可以用于创建多态的“多对多”关系:

1$videos = Video::factory()
2 ->hasTags(3, ['public' => true])
3 ->create();

定义Factories内的关系

要在模型Factories中定义关系,通常需要将一个新的Factories实例分配给该关系的外键。这通常用于“逆”关系,例如belongsTomorphTo关系。例如,如果您想在创建帖子时创建新用户,可以执行以下操作:

1use App\Models\User;
2 
3/**
4 * Define the model's default state.
5 *
6 * @return array<string, mixed>
7 */
8public function definition(): array
9{
10 return [
11 'user_id' => User::factory(),
12 'title' => fake()->title(),
13 'content' => fake()->paragraph(),
14 ];
15}

如果关系的列依赖于定义它的Factories,则可以将闭包分配给属性。闭包将接收Factories已求值的属性数组:

1/**
2 * Define the model's default state.
3 *
4 * @return array<string, mixed>
5 */
6public function definition(): array
7{
8 return [
9 'user_id' => User::factory(),
10 'user_type' => function (array $attributes) {
11 return User::find($attributes['user_id'])->type;
12 },
13 'title' => fake()->title(),
14 'content' => fake()->paragraph(),
15 ];
16}

回收现有的关系模型

如果您的模型与另一个模型共享共同关系,则可以使用该recycle方法来确保相关模型的单个实例被Factories创建的所有关系回收。

例如,假设您有AirlineFlightTicket模型,其中机票属于一家航空公司和一架航班,而航班也属于一家航空公司。创建机票时,您可能希望机票和航班使用同一家航空公司,因此您可以将航空公司实例传递给该recycle方法:

1Ticket::factory()
2 ->recycle(Airline::factory()->create())
3 ->create();

recycle如果您拥有属于共同用户或团队的模型,您可能会发现该方法特别有用。

recycle方法还接受一个现有模型的集合。当该方法传入一个集合时recycle,当Factories需要该类型的模型时,将从集合中随机选择一个模型:

1Ticket::factory()
2 ->recycle($airlines)
3 ->create();