Eloquent: 工厂
介绍
在测试应用程序或填充数据库时,您可能需要向数据库插入一些记录。与其手动指定每个列的值,不如使用 Laravel 的模型工厂为每个 Eloquent 模型 定义一组默认属性。
要查看如何编写工厂的示例,请查看应用程序中的 database/factories/UserFactory.php
文件。此工厂包含在所有新的 Laravel 应用程序中,并包含以下工厂定义:
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
/**
* 定义模型的默认状态。
*
* @return array
*/
public function definition()
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // 密码
'remember_token' => Str::random(10),
];
}
}
如您所见,工厂在其最基本的形式中是扩展了 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
方法将使用约定来确定模型的适当工厂。具体来说,该方法将在 Database\Factories
命名空间中查找一个类名与模型名匹配并以 Factory
结尾的工厂。如果这些约定不适用于您的特定应用程序或工厂,您可以覆盖模型上的 newFactory
方法以直接返回模型对应工厂的实例:
use Database\Factories\Administration\FlightFactory;
/**
* 为模型创建一个新的工厂实例。
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory()
{
return FlightFactory::new();
}
接下来,在相应的工厂上定义一个 model
属性:
use App\Administration\Flight;
use Illuminate\Database\Eloquent\Factories\Factory;
class FlightFactory extends Factory
{
/**
* 工厂对应模型的名称。
*
* @var string
*/
protected $model = Flight::class;
}
工厂状态
状态操作方法允许您定义可以以任意组合应用于模型工厂的离散修改。例如,您的 Database\Factories\UserFactory
工厂可能包含一个 suspended
状态方法,该方法修改其默认属性值之一。
状态转换方法通常调用 Laravel 基础工厂类提供的 state
方法。state
方法接受一个闭包,该闭包将接收为工厂定义的原始属性数组,并应返回要修改的属性数组:
/**
* 表示用户已被暂停。
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
public function suspended()
{
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;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
/**
* 配置模型工厂。
*
* @return $this
*/
public function configure()
{
return $this->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;
// 创建一个单一的 App\Models\User 实例...
$user = User::factory()->create();
// 创建三个 App\Models\User 实例...
$users = User::factory()->count(3)->create();
您可以通过将属性数组传递给 create
方法来覆盖工厂的默认模型属性:
$user = User::factory()->create([
'name' => 'Abigail',
]);
序列
有时您可能希望在每个创建的模型中交替更改给定模型属性的值。您可以通过将状态转换定义为序列来实现此目的。例如,您可能希望在每个创建的用户中交替更改 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
的用户。
如果需要,您可以将闭包作为序列值包含。每次序列需要新值时,都会调用闭包:
$users = User::factory()
->count(10)
->state(new Sequence(
fn ($sequence) => ['role' => UserRoles::all()->random()],
))
->create();
在序列闭包中,您可以访问注入到闭包中的序列实例的 $index
或 $count
属性。$index
属性包含到目前为止序列的迭代次数,而 $count
属性包含序列将被调用的总次数:
$users = User::factory()
->count(10)
->sequence(fn ($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
方法创建一个拥有三个帖子(posts)的用户。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();
属于关系
现在我们已经探索了如何使用工厂构建 "一对多" 关系,让我们探索关系的反向。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 的魔术工厂关系方法来定义 "属于" 关系。例如,以下示例将使用约定来确定这三个帖子应属于 Post
模型上的 user
关系:
$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 的魔术工厂关系方法来定义多对多关系。例如,以下示例将使用约定来确定相关模型应通过 User
模型上的 roles
关系方法创建:
$user = User::factory()
->hasRoles(1, [
'name' => 'Editor'
])
->create();
多态关系
多态关系 也可以使用工厂创建。多态 "多态多" 关系的创建方式与典型的 "一对多" 关系相同。例如,如果 App\Models\Post
模型与 App\Models\Comment
模型有一个 morphMany
关系:
use App\Models\Post;
$post = Post::factory()->hasComments(3)->create();
多态属于关系
不能使用魔术方法创建 morphTo
关系。相反,必须直接使用 for
方法,并且必须显式提供关系的名称。例如,假设 Comment
模型有一个定义 morphTo
关系的 commentable
方法。在这种情况下,我们可以使用 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;
/**
* 定义模型的默认状态。
*
* @return array
*/
public function definition()
{
return [
'user_id' => User::factory(),
'title' => fake()->title(),
'content' => fake()->paragraph(),
];
}
如果关系的列依赖于定义它的工厂,您可以将闭包分配给属性。闭包将接收工厂的评估属性数组:
/**
* 定义模型的默认状态。
*
* @return array
*/
public function definition()
{
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();