Eloquent:工厂
简介
在测试您的应用程序或填充数据库时,您可能需要在数据库中插入一些记录。Laravel 允许您为每个 Eloquent 模型 定义一组默认属性,而不是手动指定每一列的值,这可以通过模型工厂来实现。
要查看如何编写工厂的示例,请查看应用程序中的 database/factories/UserFactory.php
文件。此工厂包含在所有新的 Laravel 应用程序中,并包含以下工厂定义
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 Factory11{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(): array23 {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(): static37 {38 return $this->state(fn (array $attributes) => [39 'email_verified_at' => null,40 ]);41 }42}
如您所见,在其最基本的形式中,工厂是扩展 Laravel 基础工厂类并定义 definition
方法的类。definition
方法返回在使用工厂创建模型时应应用的一组默认属性值。
通过 fake
辅助函数,工厂可以访问 Faker PHP 库,该库允许您方便地为测试和填充生成各种类型的随机数据。
您可以通过更新 config/app.php
配置文件中的 faker_locale
选项来更改应用程序的 Faker 区域设置。
定义模型工厂
生成工厂
要创建工厂,请执行 make:factory
Artisan 命令
1php artisan make:factory PostFactory
新的工厂类将放置在您的 database/factories
目录中。
模型和工厂发现约定
定义工厂后,您可以使用 Illuminate\Database\Eloquent\Factories\HasFactory
trait 为模型提供的静态 factory
方法,以便为该模型实例化工厂实例。
HasFactory
trait 的 factory
方法将使用约定来确定分配给该 trait 的模型的正确工厂。具体来说,该方法将在 Database\Factories
命名空间中查找工厂,该工厂的类名与模型名称匹配,并以 Factory
为后缀。如果这些约定不适用于您的特定应用程序或工厂,您可以覆盖模型上的 newFactory
方法,以直接返回模型对应工厂的实例
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
属性
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}
工厂状态
状态操作方法允许您定义可以以任意组合应用于模型工厂的离散修改。例如,您的 Database\Factories\UserFactory
工厂可能包含一个 suspended
状态方法,该方法修改其默认属性值之一。
状态转换方法通常调用 Laravel 基础工厂类提供的 state
方法。state
方法接受一个闭包,该闭包将接收为工厂定义的原始属性数组,并且应返回要修改的属性数组
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 模型可以被 软删除,您可以调用内置的 trashed
状态方法来指示创建的模型应已“软删除”。您无需手动定义 trashed
状态,因为它会自动提供给所有工厂
1use App\Models\User;2 3$user = User::factory()->trashed()->create();
工厂回调
工厂回调使用 afterMaking
和 afterCreating
方法注册,并允许您在制作或创建模型后执行其他任务。您应该通过在工厂类上定义 configure
方法来注册这些回调。此方法将在工厂实例化时由 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(): static12 {13 return $this->afterMaking(function (User $user) {14 // ...15 })->afterCreating(function (User $user) {16 // ...17 });18 }19 20 // ...21}
您还可以在状态方法中注册工厂回调,以执行特定于给定状态的其他任务
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}
使用工厂创建模型
实例化模型
定义工厂后,您可以使用 Illuminate\Database\Eloquent\Factories\HasFactory
trait 为模型提供的静态 factory
方法,以便为该模型实例化工厂实例。让我们看一些创建模型的示例。首先,我们将使用 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
方法。只有指定的属性将被替换,而其余属性仍设置为工厂指定的默认值
1$user = User::factory()->make([2 'name' => 'Abigail Otwell',3]);
或者,可以直接在工厂实例上调用 state
方法来执行内联状态转换
1$user = User::factory()->state([2 'name' => 'Abigail Otwell',3])->make();
使用工厂创建模型时,批量赋值保护 会自动禁用。
持久化模型
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
方法来覆盖工厂的默认模型属性
1$user = User::factory()->create([2 'name' => 'Abigail',3]);
序列
有时,您可能希望为每个创建的模型交替给定模型属性的值。您可以通过将状态转换定义为序列来实现此目的。例如,您可能希望为每个创建的用户交替 admin
列的值在 Y
和 N
之间
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();
工厂关系
一对多关系
接下来,让我们探讨如何使用 Laravel 的流畅工厂方法构建 Eloquent 模型关系。首先,假设我们的应用程序具有 App\Models\User
模型和 App\Models\Post
模型。此外,假设 User
模型定义了与 Post
的 hasMany
关系。我们可以使用 Laravel 工厂提供的 has
方法创建一个具有三个帖子的用户。has
方法接受工厂实例
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 的魔术工厂关系方法来构建关系。例如,以下示例将使用约定来确定相关模型应通过 User
模型上的 posts
关系方法创建
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();
多对一关系
现在我们已经探讨了如何使用工厂构建“一对多”关系,接下来让我们探讨关系的逆关系。for
方法可用于定义工厂创建的模型所属的父模型。例如,我们可以创建三个属于单个用户的 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 的魔术工厂关系方法来定义“多对一”关系。例如,以下示例将使用约定来确定这三个帖子应属于 Post
模型上的 user
关系
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 的魔法工厂关联方法来定义多对多关系。例如,以下示例将使用约定来确定相关模型应该通过 User
模型上的 roles
关联方法来创建。
1$user = User::factory()2 ->hasRoles(1, [3 'name' => 'Editor'4 ])5 ->create();
多态关系
多态关联 也可以使用工厂创建。多态 "morph many" 关联的创建方式与典型的 "has many" 关联相同。例如,如果一个 App\Models\Post
模型与 App\Models\Comment
模型具有 morphMany
关联。
1use App\Models\Post;2 3$post = Post::factory()->hasComments(3)->create();
Morph To 关联
魔法方法不能用于创建 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();
在工厂内定义关系
为了在你的模型工厂中定义关联,你通常会将一个新的工厂实例分配给关联的外键。这通常用于 "反向" 关联,例如 belongsTo
和 morphTo
关联。例如,如果你想在创建帖子时创建一个新用户,你可以这样做。
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}
如果关联的列依赖于定义它的工厂,你可以将一个闭包分配给一个属性。该闭包将接收工厂的已评估属性数组。
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
方法来确保为工厂创建的所有关联循环使用相关模型的单个实例。
例如,假设你有 Airline
、Flight
和 Ticket
模型,其中机票属于航空公司和航班,航班也属于航空公司。在创建机票时,你可能希望机票和航班都属于同一家航空公司,因此你可以将航空公司实例传递给 recycle
方法。
1Ticket::factory()2 ->recycle(Airline::factory()->create())3 ->create();
如果你的模型属于一个共同的用户或团队,你可能会发现 recycle
方法特别有用。
recycle
方法也接受现有模型的集合。当向 recycle
方法提供集合时,当工厂需要该类型的模型时,将从集合中随机选择一个模型。
1Ticket::factory()2 ->recycle($airlines)3 ->create();