Eloquent:工厂
简介
在测试您的应用程序或填充数据库时,您可能需要在数据库中插入一些记录。Laravel 允许您使用模型工厂为每个 Eloquent 模型 定义一组默认属性,而不是手动指定每个列的值。
要查看如何编写工厂的示例,请查看您的应用程序中的 database/factories/UserFactory.php
文件。这个工厂包含在所有新的 Laravel 应用程序中,并包含以下工厂定义
namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory;use Illuminate\Support\Facades\Hash;use Illuminate\Support\Str; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> */class UserFactory extends Factory{ /** * The current password being used by the factory. */ protected static ?string $password; /** * Define the model's default state. * * @return array<string, mixed> */ 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), ]; } /** * Indicate that the model's email address should be unverified. */ public function unverified(): static { return $this->state(fn (array $attributes) => [ 'email_verified_at' => null, ]); }}
如您所见,在最基本的形式中,工厂是扩展 Laravel 的基础工厂类并定义 definition
方法的类。definition
方法返回在使用工厂创建模型时应应用的一组默认属性值。
通过 fake
助手,工厂可以访问 Faker PHP 库,这使您可以方便地生成各种用于测试和填充的随机数据。
您可以通过更新 config/app.php
配置文件中的 faker_locale
选项来更改您的应用程序的 Faker 区域设置。
定义模型工厂
生成工厂
要创建一个工厂,请执行 make:factory
Artisan 命令
php artisan make:factory PostFactory
新的工厂类将放置在您的 database/factories
目录中。
模型和工厂发现约定
定义工厂后,您可以使用 Illuminate\Database\Eloquent\Factories\HasFactory
trait 为模型提供的静态 factory
方法,以便实例化该模型的工厂实例。
HasFactory
trait 的 factory
方法将使用约定来确定分配给该 trait 的模型的适当工厂。具体来说,该方法将在 Database\Factories
命名空间中查找类名与模型名称匹配并以 Factory
为后缀的工厂。如果这些约定不适用于您的特定应用程序或工厂,您可以覆盖模型上的 newFactory
方法,以直接返回模型相应工厂的实例
use Database\Factories\Administration\FlightFactory; /** * Create a new factory instance for the model. */protected static function newFactory(){ return FlightFactory::new();}
然后,在相应的工厂上定义 model
属性
use App\Administration\Flight;use Illuminate\Database\Eloquent\Factories\Factory; class FlightFactory extends Factory{ /** * The name of the factory's corresponding model. * * @var class-string<\Illuminate\Database\Eloquent\Model> */ protected $model = Flight::class;}
工厂状态
状态操作方法允许您定义可以以任意组合应用于模型工厂的离散修改。例如,您的 Database\Factories\UserFactory
工厂可能包含一个 suspended
状态方法,该方法会修改其一个默认属性值。
状态转换方法通常会调用 Laravel 的基础工厂类提供的 state
方法。state
方法接受一个闭包,该闭包将接收为工厂定义的原始属性数组,并应返回要修改的属性数组
use Illuminate\Database\Eloquent\Factories\Factory; /** * Indicate that the user is suspended. */public function suspended(): Factory{ return $this->state(function (array $attributes) { return [ 'account_status' => 'suspended', ]; });}
“已删除”状态
如果您的 Eloquent 模型可以被 软删除,您可以调用内置的 trashed
状态方法来指示创建的模型应已“软删除”。您无需手动定义 trashed
状态,因为它会自动对所有工厂可用
use App\Models\User; $user = User::factory()->trashed()->create();
工厂回调
工厂回调使用 afterMaking
和 afterCreating
方法注册,并允许您在创建或创建模型后执行其他任务。您应该通过在工厂类上定义 configure
方法来注册这些回调。Laravel 将在实例化工厂时自动调用此方法
namespace Database\Factories; use App\Models\User;use Illuminate\Database\Eloquent\Factories\Factory; class UserFactory extends Factory{ /** * Configure the model factory. */ public function configure(): static { return $this->afterMaking(function (User $user) { // ... })->afterCreating(function (User $user) { // ... }); } // ...}
您还可以在状态方法中注册工厂回调,以执行特定于给定状态的其他任务
use App\Models\User;use Illuminate\Database\Eloquent\Factories\Factory; /** * Indicate that the user is suspended. */public function suspended(): Factory{ return $this->state(function (array $attributes) { return [ 'account_status' => 'suspended', ]; })->afterMaking(function (User $user) { // ... })->afterCreating(function (User $user) { // ... });}
使用工厂创建模型
实例化模型
定义工厂后,您可以使用 Illuminate\Database\Eloquent\Factories\HasFactory
trait 为模型提供的静态 factory
方法,以便实例化该模型的工厂实例。让我们看一些创建模型的示例。首先,我们将使用 make
方法创建模型而不将其持久化到数据库中
use App\Models\User; $user = User::factory()->make();
您可以使用 count
方法创建多个模型的集合
$users = User::factory()->count(3)->make();
应用状态
您还可以将任何 状态 应用于模型。如果您想将多个状态转换应用于模型,您可以直接调用状态转换方法
$users = User::factory()->count(5)->suspended()->make();
覆盖属性
如果您想覆盖模型的某些默认值,您可以将值数组传递给 make
方法。只会替换指定的属性,而其余属性仍将设置为工厂指定的默认值
$user = User::factory()->make([ 'name' => 'Abigail Otwell',]);
或者,可以直接在工厂实例上调用 state
方法以执行内联状态转换
$user = User::factory()->state([ 'name' => 'Abigail Otwell',])->make();
使用工厂创建模型时,会自动禁用 批量赋值保护。
持久化模型
create
方法实例化模型实例并使用 Eloquent 的 save
方法将其持久化到数据库中
use App\Models\User; // Create a single App\Models\User instance...$user = User::factory()->create(); // Create three App\Models\User instances...$users = User::factory()->count(3)->create();
您可以通过将属性数组传递给 create
方法来覆盖工厂的默认模型属性
$user = User::factory()->create([ 'name' => 'Abigail',]);
序列
有时,您可能希望为每个创建的模型交替使用给定的模型属性值。您可以通过将状态转换定义为一个序列来实现此目的。例如,您可能希望为每个创建的用户在 Y
和 N
之间交替使用 admin
列的值。
use App\Models\User;use Illuminate\Database\Eloquent\Factories\Sequence; $users = User::factory() ->count(10) ->state(new Sequence( ['admin' => 'Y'], ['admin' => 'N'], )) ->create();
在此示例中,将创建五个 admin
值为 Y
的用户,以及五个 admin
值为 N
的用户。
如有必要,您可以将闭包作为序列值包含在内。每次序列需要新值时都会调用该闭包。
use Illuminate\Database\Eloquent\Factories\Sequence; $users = User::factory() ->count(10) ->state(new Sequence( fn (Sequence $sequence) => ['role' => UserRoles::all()->random()], )) ->create();
在序列闭包中,您可以访问注入到闭包中的序列实例的 $index
或 $count
属性。$index
属性包含到目前为止序列发生的迭代次数,而 $count
属性包含序列将被调用的总次数。
$users = User::factory() ->count(10) ->sequence(fn (Sequence $sequence) => ['name' => 'Name '.$sequence->index]) ->create();
为了方便起见,也可以使用 sequence
方法应用序列,该方法只是在内部调用 state
方法。sequence
方法接受闭包或序列化属性的数组。
$users = User::factory() ->count(2) ->sequence( ['name' => 'First User'], ['name' => 'Second User'], ) ->create();
工厂关系
一对多关系
接下来,让我们探索如何使用 Laravel 的流畅工厂方法构建 Eloquent 模型关系。首先,假设我们的应用程序有一个 App\Models\User
模型和一个 App\Models\Post
模型。另外,假设 User
模型定义了与 Post
的 hasMany
关系。我们可以使用 Laravel 工厂提供的 has
方法创建一个拥有三个帖子的用户。has
方法接受一个工厂实例。
use App\Models\Post;use App\Models\User; $user = User::factory() ->has(Post::factory()->count(3)) ->create();
按照约定,当将 Post
模型传递给 has
方法时,Laravel 将假设 User
模型必须有一个 posts
方法来定义关系。如有必要,您可以显式指定您要操作的关系名称。
$user = User::factory() ->has(Post::factory()->count(3), 'posts') ->create();
当然,您可以对相关模型执行状态操作。此外,如果您的状态更改需要访问父模型,您可以传递一个基于闭包的状态转换。
$user = User::factory() ->has( Post::factory() ->count(3) ->state(function (array $attributes, User $user) { return ['user_type' => $user->type]; }) ) ->create();
使用魔术方法
为了方便起见,您可以使用 Laravel 的魔术工厂关系方法来构建关系。例如,以下示例将使用约定来确定相关模型应通过 User
模型上的 posts
关系方法创建。
$user = User::factory() ->hasPosts(3) ->create();
使用魔术方法创建工厂关系时,您可以传递一个属性数组来覆盖相关模型上的属性。
$user = User::factory() ->hasPosts(3, [ 'published' => false, ]) ->create();
如果您的状态更改需要访问父模型,您可以提供一个基于闭包的状态转换。
$user = User::factory() ->hasPosts(3, function (array $attributes, User $user) { return ['user_type' => $user->type]; }) ->create();
属于关系
现在我们已经探讨了如何使用工厂构建“has many”关系,让我们来探讨一下关系的逆向。for
方法可用于定义工厂创建的模型所属的父模型。例如,我们可以创建三个属于单个用户的 App\Models\Post
模型实例。
use App\Models\Post;use App\Models\User; $posts = Post::factory() ->count(3) ->for(User::factory()->state([ 'name' => 'Jessica Archer', ])) ->create();
如果您已经有一个应该与您正在创建的模型关联的父模型实例,您可以将模型实例传递给 for
方法。
$user = User::factory()->create(); $posts = Post::factory() ->count(3) ->for($user) ->create();
使用魔术方法
为了方便起见,您可以使用 Laravel 的魔术工厂关系方法来定义“belongs to”关系。例如,以下示例将使用约定来确定这三个帖子应属于 Post
模型上的 user
关系。
$posts = Post::factory() ->count(3) ->forUser([ 'name' => 'Jessica Archer', ]) ->create();
多对多关系
与has many 关系一样,可以使用 has
方法创建“多对多”关系。
use App\Models\Role;use App\Models\User; $user = User::factory() ->has(Role::factory()->count(3)) ->create();
透视表属性
如果您需要定义应在链接模型的透视/中间表上设置的属性,您可以使用 hasAttached
方法。此方法接受透视表属性名称和值的数组作为其第二个参数。
use App\Models\Role;use App\Models\User; $user = User::factory() ->hasAttached( Role::factory()->count(3), ['active' => true] ) ->create();
如果您的状态更改需要访问相关模型,您可以提供基于闭包的状态转换。
$user = User::factory() ->hasAttached( Role::factory() ->count(3) ->state(function (array $attributes, User $user) { return ['name' => $user->name.' Role']; }), ['active' => true] ) ->create();
如果您已经有想要附加到正在创建的模型的模型实例,您可以将模型实例传递给 hasAttached
方法。在此示例中,相同的三个角色将附加到所有三个用户。
$roles = Role::factory()->count(3)->create(); $user = User::factory() ->count(3) ->hasAttached($roles, ['active' => true]) ->create();
使用魔术方法
为了方便起见,您可以使用 Laravel 的魔术工厂关系方法来定义多对多关系。例如,以下示例将使用约定来确定相关模型应通过 User
模型上的 roles
关系方法创建。
$user = User::factory() ->hasRoles(1, [ 'name' => 'Editor' ]) ->create();
多态关系
也可以使用工厂创建多态关系。多态“morph many”关系的创建方式与典型的“has many”关系相同。例如,如果 App\Models\Post
模型与 App\Models\Comment
模型具有 morphMany
关系。
use App\Models\Post; $post = Post::factory()->hasComments(3)->create();
Morph To 关系
魔术方法不能用于创建 morphTo
关系。相反,必须直接使用 for
方法,并且必须显式提供关系的名称。例如,假设 Comment
模型有一个 commentable
方法来定义 morphTo
关系。在这种情况下,我们可以通过直接使用 for
方法创建三个属于单个帖子的评论。
$comments = Comment::factory()->count(3)->for( Post::factory(), 'commentable')->create();
多态多对多关系
多态“多对多”(morphToMany
/ morphedByMany
)关系的创建方式与非多态“多对多”关系相同。
use App\Models\Tag;use App\Models\Video; $videos = Video::factory() ->hasAttached( Tag::factory()->count(3), ['public' => true] ) ->create();
当然,魔术 has
方法也可用于创建多态“多对多”关系。
$videos = Video::factory() ->hasTags(3, ['public' => true]) ->create();
在工厂中定义关系
要在您的模型工厂中定义关系,您通常会将新的工厂实例分配给关系的外键。这通常是针对“反向”关系完成的,例如 belongsTo
和 morphTo
关系。例如,如果您想在创建帖子时创建一个新用户,您可以执行以下操作。
use App\Models\User; /** * Define the model's default state. * * @return array<string, mixed> */public function definition(): array{ return [ 'user_id' => User::factory(), 'title' => fake()->title(), 'content' => fake()->paragraph(), ];}
如果关系的列依赖于定义它的工厂,您可以将闭包分配给属性。闭包将接收工厂的已评估属性数组。
/** * Define the model's default state. * * @return array<string, mixed> */public function definition(): array{ return [ 'user_id' => User::factory(), 'user_type' => function (array $attributes) { return User::find($attributes['user_id'])->type; }, 'title' => fake()->title(), 'content' => fake()->paragraph(), ];}
为关系重用现有模型
如果您有多个模型与另一个模型共享一个共同的关系,您可以使用 recycle
方法来确保为工厂创建的所有关系重复使用相关模型的单个实例。
例如,假设您有 Airline
、Flight
和 Ticket
模型,其中机票属于航空公司和航班,而航班也属于航空公司。创建机票时,您可能希望机票和航班都使用同一家航空公司,因此您可以将航空公司实例传递给 recycle
方法。
Ticket::factory() ->recycle(Airline::factory()->create()) ->create();
如果您有属于同一用户或团队的模型,您可能会发现 recycle
方法特别有用。
recycle
方法也接受现有模型的集合。当向 recycle
方法提供集合时,当工厂需要该类型的模型时,将从集合中选择一个随机模型。
Ticket::factory() ->recycle($airlines) ->create();