Skip to content

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 ライブラリにアクセスできます。これにより、テストやシーディングのためにさまざまな種類のランダムデータを簡単に生成できます。

Note

アプリケーションの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;

/**
 * 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 ファクトリには、デフォルトの属性値の1つを変更する 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();

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カラムの値をYNで交互に変更したい場合があります。

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Sequence;

$users = User::factory()
                ->count(10)
                ->state(new Sequence(
                    ['admin' => 'Y'],
                    ['admin' => 'N'],
                ))
                ->create();

この例では、5人のユーザーがadminYで作成され、5人のユーザーがadminNで作成されます。

必要に応じて、クロージャをシーケンス値として含めることができます。クロージャは、シーケンスが新しい値を必要とするたびに呼び出されます。

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モデルインスタンスを1人のユーザーに属するように作成できます。

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();

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

モデルファクトリ内でリレーションを定義するには、通常、リレーションの外部キーに新しいファクトリインスタンスを割り当てます。これは通常、belongsTomorphToリレーションのような「逆」リレーションに対して行われます。例えば、投稿を作成する際に新しいユーザーを作成したい場合、以下のようにできます:

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メソッドを使用できます。

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

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

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

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

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

ユーザーノート