当测试你的应用程序或填充你的数据库时,你可能需要向数据库插入一些记录。与其手动指定每个列的值,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 作为后缀的工厂。如果这些约定不适用于你的特定应用程序或工厂,你可以向模型添加 UseFactory 属性,以手动指定模型的工厂:
use Illuminate\Database\Eloquent\Attributes\UseFactory;
use Database\Factories\Administration\FlightFactory;
#[UseFactory(FlightFactory::class)]
class Flight extends Model
{
// ...
}或者,您可以重写模型上的 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();[!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',
]);有时,您可能希望为每个创建的模型交替给定模型属性的值。您可以通过将状态转换定义为序列来实现此目的。例如,您可能希望为每个创建的用户交替 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属性。$index 属性包含序列迄今为止已发生的迭代次数:
$users = User::factory()
->count(10)
->state(new 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();既然我们已经探讨了如何使用工厂构建“一对多”关系,现在让我们探讨这种关系的反向。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();
$users = 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 模型与 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;
$video = Video::factory()
->hasAttached(
Tag::factory()->count(3),
['public' => true]
)
->create();自然地,神奇的 has 方法也可用于创建多态的“多对多”关系:
$video = 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();