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ライブラリにアクセスできます。これにより、テストやシーディングのためにさまざまな種類のランダムデータを簡単に生成できます。
アプリケーションの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 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`ファクトリーには、デフォルトの属性値の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();
ファクトリーを使用してモデルを作成する場合、一括割り当て保護は自動的に無効になります。
モデルの永続化
`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`カラムの値を`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();
慣例により、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();
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
メソッドを直接使用して、単一の投稿に属する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; /** * 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
メソッドを使用して、ファクトリーによって作成されたすべてのリレーションシップで関連モデルの単一インスタンスがリサイクルされるようにすることができます。
たとえば、チケットが航空会社とフライトに属し、フライトも航空会社に属する、Airline
、Flight
、および Ticket
モデルがあるとします。チケットを作成する場合、チケットとフライトの両方に同じ航空会社が必要になる可能性が高いため、航空会社インスタンスを recycle
メソッドに渡すことができます。
Ticket::factory() ->recycle(Airline::factory()->create()) ->create();
共通のユーザーまたはチームに属するモデルがある場合、recycle
メソッドは特に役立ちます。
recycle
メソッドは、既存のモデルのコレクションも受け入れます。コレクションが recycle
メソッドに提供されると、ファクトリーがそのタイプのモデルを必要とするときに、コレクションからランダムなモデルが選択されます。
Ticket::factory() ->recycle($airlines) ->create();