イントロダクション
アプリケーションのテストやデータベースの初期値生成時に、データベースへレコードを挿入する必要がある場合があるでしょう。Laravelでは、各カラムの値を手作業で指定する代わりに、Eloquentモデルそれぞれに対して、モデルファクトリを使用し、デフォルト属性セットを定義できます。
ファクトリの作成方法の例を確認するには、アプリケーションのdatabase/factories/UserFactory.php
ファイルを見てください。このファクトリはすべての新しいLaravelアプリケーションに含まれており、以下のファクトリ定義が含まれています。
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
/**
* モデルのデフォルト状態の定義
*
* @return array
*/
public function definition()
{
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ライブラリにアクセスでき、テストやシードのためにさまざまな種類のランダムデータを生成でき、便利です。
Note:
config/app.php
設定ファイルにfaker_locale
オプションを追加することで、アプリケーションのFakerロケールを設定できます。
モデルファクトリの定義
ファクトリの生成
ファクトリを作成するには、make:factory
Artisanコマンドを実行します。
php artisan make:factory PostFactory
新しいファクトリクラスは、database/factories
ディレクトリに配置されます。
Model & Factory Discovery Conventionsモデルとファクトリの
ファクトリを定義したら、モデルのファクトリインスタンスをインスタンス化するために、Illuminate\Database\Eloquent\Factories\HasFactory
トレイトが、モデルへ提供しているstaticなfactory
メソッドが使用できます。
HasFactory
トレイトのfactory
メソッドは規約に基づいて、その
トレイトが割り当てられているモデルに適したファクトリを決定します。具体的には、Database\Factories
名前空間の中でモデル名と一致するクラス名を持ち、サフィックスがFactory
であるファクトリを探します。この規約を特定のアプリケーションやファクトリで適用しない場合は、モデルのnewFactory
メソッドを上書きし、モデルと対応するファクトリのインスタンスを直接返してください。
use Database\Factories\Administration\FlightFactory;
/**
* モデルの新ファクトリ・インスタンスの生成
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory()
{
return FlightFactory::new();
}
次に、対応するファクトリで、model
プロパティを定義します。
use App\Administration\Flight;
use Illuminate\Database\Eloquent\Factories\Factory;
class FlightFactory extends Factory
{
/**
* モデルと対応するファクトリの名前
*
* @var string
*/
protected $model = Flight::class;
}
ファクトリの状態
状態操作メソッドを使用すると、モデルファクトリへ任意の組み合わせで適用できる個別の変更を定義できます。たとえば、Database\Factories\UserFactory
ファクトリに、デフォルトの属性値の1つを変更するsuspended
状態メソッドが含まれているとしましょう。
状態変換メソッドは通常、Laravelの基本ファクトリクラスが提供するstate
メソッドを呼び出します。state
メソッドは、このファクトリ用に定義する素の属性の配列を受け取るクロージャを受け入れ、変更する属性の配列を返す必要があります。
/**
* ユーザーが一時停止されていることを示す
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
public function suspended()
{
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;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
/**
* モデルファクトリの設定
*
* @return $this
*/
public function configure()
{
return $this->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();
Note: 複数代入保護は、ファクトリを使用してのモデル作成時、自動的に無効になります。
モデルの永続化
create
メソッドはモデルインスタンスをインスタンス化し、Eloquentのsave
メソッドを使用してデータベースへ永続化します。
use App\Models\User;
// App\Models\Userインスタンスを1つ生成
$user = User::factory()->create();
// App\Models\Userインスタンスを3つ生成
$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人作成されます。
必要に応じて、シーケンス値としてクロージャを含めることができます。新しい値をそのシーケンスが必要とするたびにクロージャを呼び出します。
$users = User::factory()
->count(10)
->state(new Sequence(
fn ($sequence) => ['role' => UserRoles::all()->random()],
))
->create();
シーケンスクロージャ内では,クロージャへ注入されるシーケンスインスタンスの$index
または$count
プロパティにアクセスできます。$index
プロパティには、これまでに行われたシーケンスの反復回数が格納され、$count
プロパティには、シーケンスが起動された合計回数が格納されます。
$users = User::factory()
->count(10)
->sequence(fn ($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の流暢(fluent)なファクトリメソッドを使用して、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();
規約により、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リレーション
ファクトリを使用して"has
many"リレーションを構築する方法を検討したので、逆の関係を調べてみましょう。for
メソッドを使用して、ファクトリが作成したモデルの属する親モデルを定義できます。たとえば、1人のユーザーに属する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のマジックファクトリリレーションメソッドを使用して、多対多のリレーションを定義できます。たとえば、次の例では、関連するモデルを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
モデルにmorphTo
関係を定義する
commentable
メソッドがあると想像してください。この状況でfor
メソッドを直接使用し、1つの投稿に属する3つのコメントを作成できます。
$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;
/**
* モデルのデフォルト状態の定義
*
* @return array
*/
public function definition()
{
return [
'user_id' => User::factory(),
'title' => fake()->title(),
'content' => fake()->paragraph(),
];
}
リレーションのカラムがそれを定義するファクトリに依存している場合は、属性にクロージャを割り当てることができます。クロージャは、ファクトリの評価済み属性配列を受け取ります。
/**
* モデルのデフォルト状態の定義
*
* @return array
*/
public function definition()
{
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
モデルがあり、チケット(Ticket)は航空会社(Airline)とフライト(Flight)に所属し、フライトも航空会社に所属しているとします。チケットを作成する際には、チケットとフライトの両方に同じ航空会社を使いたいでしょうから、recycle
メソッドへ航空会社のインスタンスを渡します。
Ticket::factory()
->recycle(Airline::factory()->create())
->create();
共通のユーザーやチームに所属するモデルがある場合、recycle
メソッドが特に便利だと感じるでしょう。
recycle
メソッドは、既存のモデルのコレクションを受け取ることもできます。コレクションをrecycle
メソッドへ渡すと、ファクトリがそのタイプのモデルを必要とするときに、コレクションからランダムにモデルを選びます。
Ticket::factory()
->recycle($airlines)
->create();