介绍

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

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

  1. namespace Database\Factories;
  2. use Illuminate\Database\Eloquent\Factories\Factory;
  3. use Illuminate\Support\Facades\Hash;
  4. use Illuminate\Support\Str;
  5. /**
  6. * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
  7. */
  8. class UserFactory extends Factory
  9. {
  10. /**
  11. * 被用在数据工厂的当前密码。
  12. */
  13. protected static ?string $password;
  14. /**
  15. * 定义模型的默认状态。
  16. *
  17. * @return array<string, mixed>
  18. */
  19. public function definition(): array
  20. {
  21. return [
  22. 'name' => fake()->name(),
  23. 'email' => fake()->unique()->safeEmail(),
  24. 'email_verified_at' => now(),
  25. 'password' => static::$password ??= Hash::make('password'),
  26. 'remember_token' => Str::random(10),
  27. ];
  28. }
  29. /**
  30. * 标识模型的电子邮件地址未经验证。
  31. */
  32. public function unverified(): static
  33. {
  34. return $this->state(fn (array $attributes) => [
  35. 'email_verified_at' => null,
  36. ]);
  37. }
  38. }

正如你所见,在最基本的形式中,数据工厂是继承 Laravel 基础工厂类并定义 php definition 方法的类。php definition 方法返回应在使用工厂创建模型时应用的默认属性值集合。
通过 php fake 辅助器,工厂可以访问 Faker PHP 库, 它允许你方便地生成各种用于测试和填充的随机数据。


[!注意]
你可以通过更新php config/app.php配置文件中的php faker_locale选项来更改应用程序的 Faker 区域设置。

定义模型工厂

生成工厂

为了创建工厂, 请执行 php make:factory Artisan 命令:

  1. php artisan make:factory PostFactory

新的工厂类将被放置在你的 php database/factories 目录中。

模型和工厂的发现规范
一旦你定义了工厂,你可以使用 php Illuminate\Database\Eloquent\Factories\HasFactory 特性提供的静态 php factory 方法来为该模型实例化一个工厂实例。
php HasFactory 特性的 php factory 方法将使用约定来确定分配给该特性的模型的适当工厂。具体而言,该方法将在 php Database\Factories 命名空间中查找一个类名与模型名称匹配且以 php Factory 为后缀的工厂。如果这些约定不适用于你的特定应用程序或工厂,则可以重写模型上的 php newFactory 方法以直接返回相应工厂的实例:

use Illuminate\Database\Eloquent\Factories\Factory;
use Database\Factories\Administration\FlightFactory;

  1. /**
  2. * 为该模型创建一个新的工厂实例.
  3. */
  4. protected static function newFactory(): Factory
  5. {
  6. return FlightFactory::new();
  7. }

接下来,为相应工厂定义一个 php model 属性:

  1. use App\Administration\Flight;
  2. use Illuminate\Database\Eloquent\Factories\Factory;
  3. class FlightFactory extends Factory
  4. {
  5. /**
  6. * 工厂对应的模型类名。
  7. *
  8. * @var class-string<\Illuminate\Database\Eloquent\Model>
  9. */
  10. protected $model = Flight::class;
  11. }

工厂状态

状态操作方法可以让你定义离散的修改,这些修改可以在任意组合中应用于你的模型工厂。例如,你的 php Database\Factories\UserFactory 工厂可能包含一个 php suspended 状态方法,该方法可以修改其默认属性值之一。

状态转换方法通常会调用 Laravel 基础工厂类提供的 php state 方法。php state 方法接受一个闭包函数,该函数将接收为工厂定义的原始属性数组,并应返回一个要修改的属性数组:

  1. use Illuminate\Database\Eloquent\Factories\Factory;
  2. /**
  3. * 表示用户已被暂停。
  4. */
  5. public function suspended(): Factory
  6. {
  7. return $this->state(function (array $attributes) {
  8. return [
  9. 'account_status' => 'suspended',
  10. ];
  11. });
  12. }

「Trashed」状态
如果你的 Eloquent 模型可以进行软删除,你可以调用内置的 php trashed 状态方法来表示创建的模型应该已经被「软删除」。你不需要手动定义 php trashed 状态,因为它对所有工厂都是自动可用的:

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

工厂回调

工厂回调是使用 php afterMakingphp afterCreating 方法注册的,它们允许你在生成或新创建型后执行额外任务。你应该通过在工厂类中定义一个 php configure 方法来注册这些回调。当工厂被实例化时,Laravel 将自动调用这个方法:

  1. namespace Database\Factories;
  2. use App\Models\User;
  3. use Illuminate\Database\Eloquent\Factories\Factory;
  4. class UserFactory extends Factory
  5. {
  6. /**
  7. * 配置模型工厂。
  8. */
  9. public function configure(): static
  10. {
  11. return $this->afterMaking(function (User $user) {
  12. // ...
  13. })->afterCreating(function (User $user) {
  14. // ...
  15. });
  16. }
  17. // ...
  18. }

你也可以结合 php state 方法注册工厂回调来实现指定状态下的额外任务。

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

使用工厂创建模型

实例化模型

一旦你定义了工厂,你可以使用由 php Illuminate\Database\Eloquent\Factories\HasFactory trait 为你的模型提供的 php factory 静态方法来实例化该模型的工厂对象。让我们看一些创建模型的示例。首先,我们将使用 php make 方法创建模型,而不将其持久化到数据库中:

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

你可以使用 count 方法创建多个模型的集合:

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

应用状态
你也可以将任何状态应用于这些模型。如果你想要对这些模型应用多个状态转换,只需直接调用状态转换方法即可:

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

覆盖属性
如果你想要覆盖模型的一些默认值,可以将一个值数组传递给 php make 方法。只有指定的属性将被替换,而其余的属性将保持设置为工厂指定的默认值:

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

或者,可以直接在工厂实例上调用 php state 方法进行内联状态转换:

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


技巧
使用工厂创建模型时,批量赋值保护会自动被禁用。

持久化模型

php create 方法会实例化模型并使用 Eloquent 的 php save 方法将它们持久化到数据库中:

  1. use App\Models\User;
  2. // 创建一个单独的 App\Models\User 实例...
  3. $user = User::factory()->create();
  4. // 创建三个 App\Models\User 实例...
  5. $users = User::factory()->count(3)->create();

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

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

顺序

有时,你可能希望为每个创建的模型交替更改给定模型属性的值。你可以通过将状态转换定义为顺序来实现此目的。例如,你可能希望为每个创建的用户在 php admin 列中在 php Yphp N 之间交替更改:

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

在这个例子中,将创建五个 php admin 值为 php Y 的用户和五个 php admin 值为 php N 的用户。

如果需要,你可以将闭包作为顺序值包含在内。每次顺序需要一个新值时,都会调用闭包:

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

在顺序闭包内,你可以访问注入到闭包中的顺序实例上的 php $indexphp $count 属性。 php $index 属性包含到目前为止已经进行的顺序迭代次数,而 php $count 属性包含顺序将被调用的总次数:

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

为了方便,顺序的应用也可以使用 php sequence 方法,该方法简单的在内部调用了 php state 方法。 php sequence 方法接受一个闭包或顺序的属性的数组:

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

工厂关联

一对多关系

接下来,让我们探讨如何使用 Laravel 流畅的工厂方法构建 Eloquent 模型关系。首先,假设我们的应用程序有一个 php App\Models\User 模型和一个 php App\Models\Post 模型。同时,假设 php User 模型定义了与 php Post 的一对多关系。我们可以使用 Laravel 工厂提供的 php has 方法创建一个有三篇文章的用户。php has 方法接受一个工厂实例:

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

按照约定,当将 php Post 模型传递给 php has 方法时,Laravel 将假定 php User 模型必须有一个 php 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 的魔术工厂关系方法来构建关系。例如,以下示例将使用约定确定相关模型应通过 php posts 关系方法在 php User 模型上创建:

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

在使用魔术方法创建工厂关系时,你可以传递一个属性数组,用于在相关模型上进行覆盖:

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

属于关系

现在我们已经探讨了如何使用工厂构建「has many」关系,让我们来探讨关系的反向。php for 方法可用于定义工厂创建的模型所属的父模型。例如,我们可以创建属于单个用户的三个 php App\Models\Post 模型实例:

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

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

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

使用魔术方法
为了方便起见,你可以使用 Laravel 的魔术工厂关系方法来定义「属于」关系。例如,以下示例将使用约定确定这三篇文章应该属于 php Post 模型上的 php user 关系:

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

多对多关系

类似于一对多关系,可以使用 php has 方法创建「多对多」关系:

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

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

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

如果你已经有模型实例希望附加到你正在创建的模型上,你可以将模型实例传递给 php hasAttached 方法。在这个示例中,相同的三个角色将附加给所有三个用户:

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

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

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

多态关系

多态关系 也可以使用工厂创建。多态「morph many」关系的创建方式与典型的「has many」关系相同。例如,如果 php App\Models\Post 模型与 php App\Models\Comment 模型具有 php morphMany 关系:

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

Morph To 关系
不能使用魔术方法创建 php morphTo 关系。相反,必须直接使用 php for 方法,并明确提供关系的名称。例如,假设 php Comment 模型具有一个 php commentable 方法定义了一个 php morphTo 关系。在这种情况下,我们可以通过直接使用 php for 方法创建属于单个帖子的三条评论:

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

多态多对多关系
多态「多对多」(php morphToMany / php morphedByMany)关系的创建方式与非多态「多对多」关系相同:

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

当然,魔术 php has 方法也可以用于创建多态「多对多」关系:

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

在工厂中定义关系

要在你的模型工厂中定义关系,通常会将一个新的工厂实例分配给关系的外键。这通常用于「反向」关系,如 php belongsTophp morphTo 关系。例如,如果你想在创建帖子时创建一个新用户,你可以这样做:

  1. use App\Models\User;
  2. /**
  3. * 定义模型的默认状态。
  4. *
  5. * @return array<string, mixed>
  6. */
  7. public function definition(): array
  8. {
  9. return [
  10. 'user_id' => User::factory(),
  11. 'title' => fake()->title(),
  12. 'content' => fake()->paragraph(),
  13. ];
  14. }

如果关系的列取决于定义它的工厂,你可以将闭包分配给一个属性。闭包将接收工厂评估的属性数组:

  1. /**
  2. * 定义模型的默认状态。
  3. *
  4. * @return array<string, mixed>
  5. */
  6. public 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. }

重复使用现有模型来建立关系

如果你有多个模型与另一个模型共享一个常见关系,你可以使用 php recycle 方法确保相关模型的单个实例被重复使用,用于工厂创建的所有关系。

举例来说,假设你有 php Airlinephp Flightphp Ticket 模型,其中机票属于一家航空公司和一个航班,而航班也隶属于一家航空公司。在创建机票时,你可能希望为机票和航班选择相同的航空公司,因此可以将一家航空公司实例传递给 php recycle 方法:

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

如果你的模型属于公共用户或团队,你可能会发现 php recycle 方法特别有用。

php recycle 方法还接受一组现有模型。当将一组模型提供给 php recycle 方法时,工厂需要该类型的模型时会从集合中随机选择一个模型:

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