跳到內容

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 Factory
11{
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(): array
23 {
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(): static
37 {
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();

工廠回呼

工廠回呼使用 afterMakingafterCreating 方法註冊,並允許您在建立或建立模型後執行其他任務。您應該透過在工廠類別上定義 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(): static
12 {
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 欄位的值在 YN 之間

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();

工廠關聯

Has Many 關聯

接下來,讓我們探索使用 Laravel 的流暢工廠方法建立 Eloquent 模型關聯。首先,假設我們的應用程式有一個 App\Models\User 模型和一個 App\Models\Post 模型。此外,假設 User 模型定義了與 PosthasMany 關聯。我們可以使用 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();

Belongs To 關聯

現在我們已經探索了如何使用工廠建立「has many」關聯,讓我們探索關聯的反向。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 的魔法工廠關聯方法來定義「屬於於 (belongs to)」關聯。例如,以下範例將使用慣例來判斷這三篇文章應「屬於於」Post 模型上的 user 關聯

1$posts = Post::factory()
2 ->count(3)
3 ->forUser([
4 'name' => 'Jessica Archer',
5 ])
6 ->create();

Many to Many 關聯

如同一對多關聯,「多對多」關聯可以使用 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();

多態關聯

多型關聯也可以使用工廠建立。多型「一對多」關聯的建立方式與典型的「一對多」關聯相同。例如,如果 App\Models\Post 模型與 App\Models\Comment 模型具有 morphMany 關聯

1use App\Models\Post;
2 
3$post = Post::factory()->hasComments(3)->create();

Morph To 關聯

魔法方法不能用於建立 morphTo 關聯。相反地,必須直接使用 for 方法,並且必須明確提供關聯的名稱。例如,假設 Comment 模型具有一個定義 morphTo 關聯的 commentable 方法。在這種情況下,我們可以透過直接使用 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();

在工廠中定義關聯

若要在您的模型工廠中定義關聯,您通常會將新的工廠實例指派給關聯的外鍵。這通常適用於「反向」關聯,例如 belongsTomorphTo 關聯。例如,如果您想在建立文章時建立新使用者,您可以執行以下操作

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 方法來確保為工廠建立的所有關聯回收相關模型的單一實例。

例如,假設您有 AirlineFlightTicket 模型,其中機票屬於航空公司和航班,而航班也屬於航空公司。在建立機票時,您可能希望機票和航班都屬於同一家航空公司,因此您可以將航空公司實例傳遞給 recycle 方法

1Ticket::factory()
2 ->recycle(Airline::factory()->create())
3 ->create();

如果您有模型屬於共同的使用者或團隊,您可能會發現 recycle 方法特別有用。

recycle 方法也接受現有模型的集合。當為 recycle 方法提供集合時,當工廠需要該類型的模型時,將從集合中隨機選擇一個模型

1Ticket::factory()
2 ->recycle($airlines)
3 ->create();

Laravel 是最具生產力的方式,能夠
建構、部署和監控軟體。