跳至內容

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 函式庫,這可讓您方便地產生各種隨機資料以進行測試和播種。

lightbulb

您可以透過更新 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();

工廠回呼

工廠回呼會使用 afterMakingafterCreating 方法註冊,並讓您在建立或建立模型後執行其他工作。您應該透過在工廠類別上定義 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();
lightbulb

使用工廠建立模型時,會自動停用大量指派保護

持久化模型

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 欄位的值在 YN 之間交替。

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 模型定義了與 PosthasMany 關係。我們可以透過使用 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();

在工廠中定義關聯

若要在您的模型工廠中定義關係,您通常會將新的工廠實例指派給關係的外鍵。這通常是針對「反向」關係完成的,例如 belongsTomorphTo 關係。例如,如果您想在建立帖子時建立新的使用者,您可以執行以下操作:

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

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

Ticket::factory()
->recycle(Airline::factory()->create())
->create();

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

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

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