メインコンテンツまでスキップ

エロクエント: ファクトリ

はじめに

アプリケーションのテストやデータベースのシード時に、データベースにいくつかのレコードを挿入する必要があるかもしれません。手動で各列の値を指定する代わりに、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 ディレクトリに配置されます。

モデルとファクトリの検出規則

ファクトリを定義した後は、モデルに提供される静的 factory メソッドを使用して、そのモデル用のファクトリインスタンスをインスタンス化できます。これは Illuminate\Database\Eloquent\Factories\HasFactory トレイトによって提供されます。

HasFactory トレイトの factory メソッドは、モデルに割り当てられたトレイトに適用される適切なファクトリを決定するための規則を使用します。具体的には、このメソッドは、モデル名と Factory で接尾辞が付いたクラス名と一致する Database\Factories ネームスペース内のファクトリを探します。これらの規則が特定のアプリケーションやファクトリに適用されない場合は、モデルで newFactory メソッドを上書きして、モデルに対応するファクトリのインスタンスを直接返すように定義できます:

    use Illuminate\Database\Eloquent\Factories\Factory;
use Database\Factories\Administration\FlightFactory;

/**
* Create a new factory instance for the model.
*/
protected static function newFactory(): Factory
{
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',
];
});
}

"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
{
/**
* 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 列の値を 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 のユーザーが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();

慣例として、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 メソッドを使用して、ファクトリで作成されたモデルが属する親モデルを定義することができます。たとえば、単一のユーザーに属する 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();

多対多の関係

has many relationshipsと同様に、「多対多」の関係は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モデルがcommentableメソッドを持ち、morphToリレーションシップを定義している場合、forメソッドを直接使用して単一の投稿に属する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();

ファクトリ内でのリレーションシップの定義

モデルファクトリ内でリレーションシップを定義するには、通常、リレーションシップの外部キーに新しいファクトリインスタンスを割り当てます。これは通常、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 メソッドを使用して、ファクトリによって作成されたすべてのリレーションシップで関連モデルの単一のインスタンスが再利用されるようにします。

たとえば、AirlineFlight、および Ticket モデルがあり、チケットが航空会社とフライトに属し、フライトも航空会社に属しているとします。チケットを作成するときは、おそらくチケットとフライトの両方に同じ航空会社を使用したいので、recycle メソッドに航空会社のインスタンスを渡すことができます:

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

recycle メソッドは、共通のユーザーまたはチームに属するモデルがある場合に特に便利です。

recycle メソッドは、既存モデルのコレクションも受け入れます。コレクションが recycle メソッドに提供されると、ファクトリがそのタイプのモデルが必要なときにコレクションからランダムなモデルが選択されます:

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