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
為後綴。如果這些慣例不適用於您的特定應用程式或工廠,您可以覆寫模型上的 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();
工廠回呼
工廠回呼使用 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();
在此範例中,將建立五個 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
模型定義了與 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 關聯
現在我們已經探索了如何使用工廠建立「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();
在工廠中定義關聯
若要在您的模型工廠中定義關聯,您通常會將新的工廠實例指派給關聯的外鍵。這通常適用於「反向」關聯,例如 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();