雄辩:工厂
Introduction
在测试您的应用程序或为您的数据库做种时,您可能需要将一些记录插入到您的数据库中。 Laravel 允许您为每个列定义一组默认属性,而不是手动指定每一列的值雄辩的模型 使用模型工厂。
要查看如何编写工厂的示例,请查看database/factories/UserFactory.php
在您的申请文件中。这个工厂包含在所有新的 Laravel 应用程序中,并包含以下工厂定义:
namespace Database\Factories;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Factories\Factory;
class UserFactory extends Factory
{
/**
* 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' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
}
}
如您所见,在最基本的形式中,工厂是扩展 Laravel 基工厂类并定义一个definition
方法。这definition
方法返回使用工厂创建模型时应应用的默认属性值集。
通过fake
帮手,工厂可以访问Faker PHP 库,可让您方便地生成各种随机数据以进行测试和播种。
Note
你可以通过添加一个faker_locale
选择你的config/app.php
配置文件。
定义模型工厂
发电厂
要创建工厂,请执行make:factory
工匠命令:
php artisan make:factory PostFactory
新的工厂类将放置在您的database/factories
目录。
模型和工厂发现约定
一旦你定义了你的工厂,你可以使用静态factory
方法提供给你的模型Illuminate\Database\Eloquent\Factories\HasFactory
trait 以便实例化该模型的工厂实例。
这HasFactory
特质的factory
方法将使用约定来确定特征分配到的模型的正确工厂。具体来说,该方法将在Database\Factories
具有与模型名称匹配的类名称并以Factory
.如果这些约定不适用于您的特定应用程序或工厂,您可以覆盖newFactory
模型上的方法直接返回模型对应工厂的实例:
use Illuminate\Database\Eloquent\Factories\Factory;
use Database\Factories\Administration\FlightFactory;
/**
* Create a new factory instance for the model.
*/
protected static function newFactory(): Factory
{
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 string
*/
protected $model = Flight::class;
}
工厂状态
状态操作方法允许您定义可以以任意组合应用于模型工厂的离散修改。例如,您的Database\Factories\UserFactory
工厂可能包含suspended
修改其默认属性值之一的状态方法。
状态转换方法通常调用state
Laravel 的基工厂类提供的方法。这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
state 方法指示创建的模型应该已经被“软删除”。您无需手动定义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;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
/**
* Configure the model factory.
*/
public function configure(): static
{
return $this->afterMaking(function (User $user) {
// ...
})->afterCreating(function (User $user) {
// ...
});
}
// ...
}
使用工厂创建模型
实例化模型
一旦你定义了你的工厂,你可以使用静态factory
方法提供给你的模型Illuminate\Database\Eloquent\Factories\HasFactory
trait 以便实例化该模型的工厂实例。让我们看几个创建模型的例子。首先,我们将使用make
创建模型而不将它们保存到数据库的方法:
use App\Models\User;
$user = User::factory()->make();
您可以使用创建许多模型的集合count
方法:
$users = User::factory()->count(3)->make();
申请国
您也可以申请您的任何states 到模型。如果您想对模型应用多个状态转换,您可以直接调用状态转换方法:
$users = User::factory()->count(5)->suspended()->make();
覆盖属性
如果您想覆盖模型的某些默认值,您可以将一个值数组传递给make
方法。只有指定的属性将被替换,而其余属性将保持设置为工厂指定的默认值:
$user = User::factory()->make([
'name' => 'Abigail Otwell',
]);
或者,state
可以直接在工厂实例上调用方法来执行内联状态转换:
$user = User::factory()->state([
'name' => 'Abigail Otwell',
])->make();
Note
批量赋值保护 使用工厂创建模型时自动禁用。
持久模型
这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',
]);
Sequences
有时您可能希望为每个创建的模型替换给定模型属性的值。您可以通过将状态转换定义为序列来完成此操作。例如,您可能希望改变一个值admin
列之间Y
和N
对于每个创建的用户:
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
模型定义了一个hasMany
有关系Post
.我们可以创建一个拥有三个帖子的用户,使用has
Laravel 工厂提供的方法。这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 的神奇工厂关系方法来建立关系。例如,以下示例将使用约定来确定相关模型应通过posts
上的关系法User
模型:
$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();
属于关系
现在我们已经探索了如何使用工厂建立“有很多”关系,让我们探索关系的逆向。这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 的神奇工厂关系方法来定义“属于”关系。例如,下面的例子将使用约定来确定这三个帖子应该属于user
上的关系Post
模型:
$posts = Post::factory()
->count(3)
->forUser([
'name' => 'Jessica Archer',
])
->create();
多对多关系
喜欢有很多关系,可以使用创建“多对多”关系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 的神奇工厂关系方法来定义多对多关系。例如,以下示例将使用约定来确定相关模型应通过roles
上的关系法User
模型:
$user = User::factory()
->hasRoles(1, [
'name' => 'Editor'
])
->create();
多态关系
多态关系 也可以使用工厂创建。多态“morph many”关系的创建方式与典型的“has many”关系相同。例如,如果一个App\Models\Post
模型有一个morphMany
与一个关系App\Models\Comment
模型:
use App\Models\Post;
$post = Post::factory()->hasComments(3)->create();
变形为关系
魔术方法不能用于创建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();