Eloquent:工廠
簡介
在測試應用程式或播種資料庫時,您可能需要在資料庫中插入一些記錄。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
特性提供給模型的靜態 factory
方法,以便為該模型實例化工廠實例。
HasFactory
特性的 factory
方法將使用慣例來判斷指派給該特性的模型的正確工廠。具體而言,該方法將在 Database\Factories
名稱空間中尋找具有與模型名稱相符的類別名稱,並以 Factory
為後綴的工廠。如果這些慣例不適用於您的特定應用程式或工廠,您可以覆寫模型上的 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
特性提供給模型的靜態 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; // 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
或 $count
屬性。$index
屬性包含到目前為止已發生的序列迭代次數,而 $count
屬性包含將呼叫序列的總次數。
$users = User::factory() ->count(10) ->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();
工廠關聯
Has Many 關聯
接下來,讓我們探索如何使用 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();
Belongs To 關聯
現在我們已經探索了如何使用工廠建立「多對一」關係,讓我們探索關係的反向。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();
多型關聯
多態關係也可以使用工廠建立。多態「morph 多個」關係的建立方式與典型的「多對一」關係相同。例如,如果 App\Models\Post
模型與 App\Models\Comment
模型具有 morphMany
關係。
use App\Models\Post; $post = Post::factory()->hasComments(3)->create();
Morph To 關係
魔術方法不能用於建立 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; /** * 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();