#はじめに
アプリケーションのテストやデータベースのシーディング時に、データベースにいくつかのレコードを挿入する必要があります。各カラムの値を手動で指定する代わりに、Laravelではモデルファクトリーを使って各Eloquentモデルのデフォルト属性セットを定義できます。
ファクトリーの書き方の例を見るには、アプリケーションの database/factories/UserFactory.php ファイルを確認してください。このファクトリーは新しいLaravelアプリケーションにすべて含まれており、以下のファクトリー定義が含まれています:
namespace Database\Factories;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Factories\Factory;
class UserFactory extends Factory
{
/**
* モデルのデフォルト状態を定義します。
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
}
}
ご覧の通り、ファクトリーは基本的にLaravelのベースファクトリークラスを継承したクラスで、definition メソッドを定義します。definition メソッドはファクトリーを使ってモデルを作成する際に適用されるデフォルトの属性値セットを返します。
fake ヘルパーを通じて、ファクトリーはテストやシーディング用に様々な種類のランダムデータを簡単に生成できる Faker PHPライブラリにアクセスできます。
アプリケーションのFakerロケールは、config/app.php 設定ファイルに faker_locale オプションを追加して設定できます。
#モデルファクトリーの定義
#ファクトリーの生成
ファクトリーを作成するには、make:factory の Artisanコマンドを実行します:
php artisan make:factory PostFactory
新しいファクトリークラスは database/factories ディレクトリに配置されます。
#モデルとファクトリーの検出規約
ファクトリーを定義したら、Illuminate\Database\Eloquent\Factories\HasFactory トレイトがモデルに提供する静的な factory メソッドを使って、そのモデルのファクトリーインスタンスを生成できます。
HasFactory トレイトの factory メソッドは、割り当てられたモデルに対して適切なファクトリーを規約に基づいて判別します。具体的には、Database\Factories 名前空間内でモデル名に一致し、Factory で終わるクラス名のファクトリーを探します。これらの規約が特定のアプリケーションやファクトリーに合わない場合は、モデルの newFactory メソッドをオーバーライドして、対応するファクトリーのインスタンスを直接返すようにできます:
use Illuminate\Database\Eloquent\Factories\Factory;
use Database\Factories\Administration\FlightFactory;
/**
* モデルの新しいファクトリーインスタンスを作成します。
*/
protected static function newFactory(): Factory
{
return FlightFactory::new();
}
対応するファクトリーに model プロパティを定義します:
use App\Administration\Flight;
use Illuminate\Database\Eloquent\Factories\Factory;
class FlightFactory extends Factory
{
/**
* ファクトリーに対応するモデルの名前。
*
* @var class-string<\Illuminate\Database\Eloquent\Model>
*/
protected $model = Flight::class;
}
#ファクトリーステート
ステート操作メソッドを使うと、モデルファクトリーに対して任意の組み合わせで適用できる個別の変更を定義できます。例えば、Database\Factories\UserFactory ファクトリーに、デフォルト属性の一つを変更する suspended ステートメソッドを含めることができます。
ステート変換メソッドは通常、Laravelのベースファクトリークラスが提供する state メソッドを呼び出します。state メソッドはクロージャを受け取り、そのクロージャはファクトリーで定義された生の属性配列を受け取り、変更する属性の配列を返す必要があります:
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* ユーザーが停止中であることを示します。
*/
public function suspended(): Factory
{
return $this->state(function (array $attributes) {
return [
'account_status' => 'suspended',
];
});
}
#「Trashed」ステート
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
{
/**
* モデルファクトリーを設定します。
*/
public function configure(): static
{
return $this->afterMaking(function (User $user) {
// ...
})->afterCreating(function (User $user) {
// ...
});
}
// ...
}
ステートメソッド内でもファクトリーコールバックを登録して、特定のステートに固有の追加処理を行えます:
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* ユーザーが停止中であることを示します。
*/
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;
// 単一の App\Models\User インスタンスを作成...
$user = User::factory()->create();
// 3つの App\Models\User インスタンスを作成...
$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 のユーザーが5人作成され、admin の値が N のユーザーが5人作成されます。
必要に応じて、シーケンス値としてクロージャを含めることもできます。クロージャはシーケンスが新しい値を必要とするたびに呼び出されます:
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 メソッドを使って、3つの投稿を持つユーザーを作成できます。has メソッドはファクトリインスタンスを受け取ります:
use App\Models\Post;
use App\Models\User;
$user = User::factory()
->has(Post::factory()->count(3))
->create();
慣例として、has メソッドに Post モデルを渡すと、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 リレーションシップ
「has many」リレーションシップの構築方法を確認したので、次はその逆のリレーションシップを見ていきましょう。for メソッドは、ファクトリで作成したモデルが属する親モデルを定義するために使います。例えば、単一のユーザーに属する3つの 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のマジックファクトリリレーションシップメソッドを使って「belongs to」リレーションシップを定義できます。例えば、以下の例は慣例に従い、3つの投稿が Post モデルの user リレーションシップに属することを示します:
$posts = Post::factory()
->count(3)
->forUser([
'name' => 'Jessica Archer',
])
->create();
#Many to Many リレーションシップ
has many リレーションシップと同様に、「many to many」リレーションシップも has メソッドを使って作成できます:
use App\Models\Role;
use App\Models\User;
$user = User::factory()
->has(Role::factory()->count(3))
->create();
#ピボットテーブルの属性
モデルをつなぐピボット(中間)テーブルに設定すべき属性を定義する必要がある場合は、hasAttached メソッドを使えます。このメソッドは2番目の引数にピボットテーブルの属性名と値の配列を受け取ります:
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 メソッドに渡せます。この例では、同じ3つのロールが3人のユーザーすべてに関連付けられます:
$roles = Role::factory()->count(3)->create();
$user = User::factory()
->count(3)
->hasAttached($roles, ['active' => true])
->create();
#マジックメソッドの使用
利便性のために、Laravelのマジックファクトリリレーションシップメソッドを使って many to many リレーションシップを定義できます。例えば、以下の例は慣例に従い、関連モデルが User モデルの roles リレーションシップメソッドを通じて作成されることを示します:
$user = User::factory()
->hasRoles(1, [
'name' => 'Editor'
])
->create();
#ポリモーフィックリレーションシップ
ポリモーフィックリレーションシップもファクトリで作成できます。ポリモーフィックの「morph many」リレーションシップは、通常の「has many」リレーションシップと同様に作成します。例えば、App\Models\Post モデルが App\Models\Comment モデルと morphMany リレーションシップを持つ場合:
use App\Models\Post;
$post = Post::factory()->hasComments(3)->create();
#Morph To リレーションシップ
マジックメソッドは morphTo リレーションシップの作成には使えません。その代わりに、for メソッドを直接使い、リレーションシップ名を明示的に指定する必要があります。例えば、Comment モデルが commentable メソッドで morphTo リレーションシップを定義している場合、for メソッドを使って単一の投稿に属する3つのコメントを作成できます:
$comments = Comment::factory()->count(3)->for(
Post::factory(), 'commentable'
)->create();
#ポリモーフィック many to many リレーションシップ
ポリモーフィックの「many to many」(morphToMany / morphedByMany)リレーションシップは、非ポリモーフィックの「many to many」リレーションシップと同様に作成できます:
use App\Models\Tag;
use App\Models\Video;
$videos = Video::factory()
->hasAttached(
Tag::factory()->count(3),
['public' => true]
)
->create();
もちろん、マジックの has メソッドもポリモーフィックの「many to many」リレーションシップの作成に使えます:
$videos = Video::factory()
->hasTags(3, ['public' => true])
->create();
#ファクトリ内でのリレーションシップ定義
モデルファクトリ内でリレーションシップを定義するには、通常、リレーションシップの外部キーに新しいファクトリインスタンスを割り当てます。これは通常、belongsTo や morphTo のような「逆」リレーションシップで行います。例えば、投稿を作成するときに新しいユーザーも作成したい場合、以下のようにします:
use App\Models\User;
/**
* モデルのデフォルト状態を定義します。
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'title' => fake()->title(),
'content' => fake()->paragraph(),
];
}
リレーションシップのカラムがそれを定義するファクトリに依存する場合、属性にクロージャを割り当てられます。クロージャはファクトリの評価済み属性配列を受け取ります:
/**
* モデルのデフォルト状態を定義します。
*
* @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 メソッドは、既存の Model のコレクションも受け取ります。recycle にコレクションを渡すと、ファクトリがその型の Model を必要とした際に、コレクションからランダムに Model が選択されます:
Ticket::factory()
->recycle($airlines)
->create();