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 Factory11{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(): array23 {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(): static37 {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 為字尾的工廠。如果這些約定不適用於您的特定應用程式或工廠,您可以在模型上新增 UseFactory 屬性來手動指定模型的工廠:
1use Illuminate\Database\Eloquent\Attributes\UseFactory;2use Database\Factories\Administration\FlightFactory;3 4#[UseFactory(FlightFactory::class)]5class Flight extends Model6{7 // ...8}
或者,您可以重寫模型上的 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}
然後,在相應的工廠上使用 UseModel 屬性來指定模型:
1use App\Administration\Flight;2use Illuminate\Database\Eloquent\Factories\Attributes\UseModel;3use Illuminate\Database\Eloquent\Factories\Factory;4 5#[UseModel(Flight::class)]6class FlightFactory extends Factory7{8 // ...9}
工廠狀態
狀態操作方法允許您定義離散的修改,這些修改可以以任意組合應用於您的模型工廠。例如,您的 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();
工廠回撥
工廠回撥是使用 afterMaking 和 afterCreating 方法註冊的,允許您在製造或建立模型後執行額外的任務。您應該透過在工廠類上定義 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(): static12 {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 列的值之間在 Y 和 N 之間交替:
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();
在此示例中,將建立 5 個 admin 值為 Y 的使用者,以及 5 個 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 屬性。$index 屬性包含到目前為止序列已經進行的迭代次數:
1$users = User::factory()2 ->count(10)3 ->state(new Sequence(4 fn (Sequence $sequence) => ['name' => 'Name '.$sequence->index],5 ))6 ->create();
為了方便起見,也可以使用 sequence 方法來應用序列,該方法在內部簡單地呼叫了 state 方法。sequence 方法接受一個閉包或序列化屬性的陣列:
1$users = User::factory()2 ->count(2)3 ->sequence(4 ['name' => 'First User'],5 ['name' => 'Second User'],6 )7 ->create();
工廠關聯
一對多關聯
接下來,讓我們探索使用 Laravel 的流暢工廠方法構建 Eloquent 模型關聯。首先,假設我們的應用程式有一個 App\Models\User 模型和一個 App\Models\Post 模型。此外,假設 User 模型定義了與 Post 的 hasMany 關聯。我們可以使用 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)
現在我們已經探索瞭如何使用工廠構建“一對多”關聯,讓我們探索該關聯的逆向。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 的魔術工廠關聯方法來定義“從屬”關聯。例如,以下示例將使用約定來確定這三篇文章應該屬於 Post 模型上的 user 關聯:
1$posts = Post::factory()2 ->count(3)3 ->forUser([4 'name' => 'Jessica Archer',5 ])6 ->create();
多對多關聯
與 一對多關聯 一樣,“多對多”關聯可以使用 has 方法建立:
1use App\Models\Role;2use App\Models\User;3 4$user = User::factory()5 ->has(Role::factory()->count(3))6 ->create();
中間表屬性
如果您需要定義應該在連結模型的中間表(Pivot Table)上設定的屬性,您可以使用 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$users = 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();
多型關聯
多型關聯 也可以使用工廠建立。多型“一對多 (morphMany)”關聯的建立方式與典型“一對多 (hasMany)”關聯相同。例如,如果 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$video = Video::factory()5 ->hasAttached(6 Tag::factory()->count(3),7 ['public' => true]8 )9 ->create();
當然,魔術 has 方法也可以用於建立多型“多對多”關聯:
1$video = Video::factory()2 ->hasTags(3, ['public' => true])3 ->create();
在工廠內定義關聯
要在您的模型工廠內定義關聯,您通常會將一個新的工廠例項分配給該關聯的外部索引鍵。這通常針對諸如 belongsTo 和 morphTo 關聯之類的“逆向”關聯完成。例如,如果您想在建立文章時建立一個新使用者,您可以執行以下操作:
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 方法來確保關聯模型的單個例項被工廠建立的所有關聯所複用。
例如,假設您有 Airline、Flight 和 Ticket 模型,其中機票屬於航空公司和航班,航班也屬於航空公司。在建立機票時,您可能希望機票和航班使用同一家航空公司,因此您可以將一個航空公司例項傳遞給 recycle 方法:
1Ticket::factory()2 ->recycle(Airline::factory()->create())3 ->create();
如果您有屬於同一個使用者或團隊的模型,您會發現 recycle 方法特別有用。
recycle 方法也接受現有模型的集合。當向 recycle 方法提供集合時,當工廠需要該型別的模型時,將從集合中選擇一個隨機模型。
1Ticket::factory()2 ->recycle($airlines)3 ->create();