Eloquent:ファクトリ
イントロダクション
アプリケーションのテストやデータベースのシーディング(初期データ投入)の際に、データベースにいくつかのレコードを挿入する必要があるかもしれません。各カラムの値を手動で指定する代わりに、Laravelではモデルファクトリを使用して、各Eloquentモデルのデフォルト属性セットを定義できます。
ファクトリの書き方の例を見るには、アプリケーションのdatabase/factories/UserFactory.phpファイルを確認してください。このファクトリは、すべての新しいLaravelアプリケーションに含まれており、以下のファクトリ定義を含んでいます。
1namespace Database\Factories; 2 3use Illuminate\Database\Eloquent\Factories\Factory; 4use Illuminate\Support\Facades\Hash; 5use Illuminate\Support\Str; 6 7/** 8 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> 9 */10class UserFactory extends Factory11{12 /**13 * The current password being used by the factory.14 */15 protected static ?string $password;16 17 /**18 * Define the model's default state.19 *20 * @return array<string, mixed>21 */22 public function definition(): array23 {24 return [25 'name' => fake()->name(),26 'email' => fake()->unique()->safeEmail(),27 'email_verified_at' => now(),28 'password' => static::$password ??= Hash::make('password'),29 'remember_token' => Str::random(10),30 ];31 }32 33 /**34 * Indicate that the model's email address should be unverified.35 */36 public function unverified(): static37 {38 return $this->state(fn (array $attributes) => [39 'email_verified_at' => null,40 ]);41 }42}
ご覧の通り、最も基本的な形式では、ファクトリはLaravelのベースファクトリクラスを拡張し、definitionメソッドを定義するクラスです。definitionメソッドは、ファクトリを使用してモデルを作成する際に適用されるべき属性値のデフォルトセットを返します。
fakeヘルパを介して、ファクトリはFaker PHPライブラリにアクセスできます。これにより、テストやシーディングのためにさまざまな種類のランダムデータを便利に生成できます。
アプリケーションのFakerロケールは、config/app.php設定ファイルのfaker_localeオプションを更新することで変更できます。
モデルファクトリの定義
ファクトリの生成
ファクトリを作成するには、make:factory Artisanコマンドを実行します。
1php artisan make:factory PostFactory
新しいファクトリクラスはdatabase/factoriesディレクトリに配置されます。
モデルとファクトリの発見規則
ファクトリを定義したら、Illuminate\Database\Eloquent\Factories\HasFactoryトレイトによってモデルに提供される静的なfactoryメソッドを使用して、そのモデルのファクトリインスタンスを生成できます。
HasFactoryトレイトのfactoryメソッドは、規約を使用して、トレイトが割り当てられているモデルの適切なファクトリを決定します。具体的には、このメソッドはDatabase\Factories名前空間内で、モデル名に一致し、接尾辞がFactoryであるクラス名のファクトリを探します。これらの規約が特定のアプリケーションやファクトリに適用されない場合は、モデルのnewFactoryメソッドをオーバーライドして、モデルに対応するファクトリのインスタンスを直接返すことができます。
1use Database\Factories\Administration\FlightFactory;2 3/**4 * Create a new factory instance for the model.5 */6protected static function newFactory()7{8 return FlightFactory::new();9}
次に、対応するファクトリにmodelプロパティを定義します。
1use App\Administration\Flight; 2use Illuminate\Database\Eloquent\Factories\Factory; 3 4class FlightFactory extends Factory 5{ 6 /** 7 * The name of the factory's corresponding model. 8 * 9 * @var class-string<\Illuminate\Database\Eloquent\Model>10 */11 protected $model = Flight::class;12}
ファクトリの状態
状態操作メソッドを使用すると、モデルファクトリに任意の組み合わせで適用できる個別の変更を定義できます。たとえば、Database\Factories\UserFactoryファクトリには、デフォルトの属性値の1つを変更するsuspended状態メソッドを含めることができます。
状態変換メソッドは通常、Laravelのベースファクトリクラスが提供するstateメソッドを呼び出します。stateメソッドは、ファクトリに定義された生の属性の配列を受け取るクロージャを引数に取り、変更する属性の配列を返す必要があります。
1use Illuminate\Database\Eloquent\Factories\Factory; 2 3/** 4 * Indicate that the user is suspended. 5 */ 6public function suspended(): Factory 7{ 8 return $this->state(function (array $attributes) { 9 return [10 'account_status' => 'suspended',11 ];12 });13}
「削除済み(Trashed)」状態
Eloquentモデルがソフトデリート可能である場合、組み込みのtrashed状態メソッドを呼び出して、作成されたモデルがすでに「ソフトデリート」済みであることを示すことができます。trashed状態はすべてのファクトリで自動的に利用可能であるため、手動で定義する必要はありません。
1use App\Models\User;2 3$user = User::factory()->trashed()->create();
ファクトリのコールバック
ファクトリのコールバックは、afterMakingメソッドとafterCreatingメソッドを使用して登録され、モデルの作成(making)または生成(creating)後に追加のタスクを実行できます。これらのコールバックは、ファクトリクラスにconfigureメソッドを定義することで登録する必要があります。このメソッドは、ファクトリがインスタンス化されるときにLaravelによって自動的に呼び出されます。
1namespace Database\Factories; 2 3use App\Models\User; 4use Illuminate\Database\Eloquent\Factories\Factory; 5 6class UserFactory extends Factory 7{ 8 /** 9 * Configure the model factory.10 */11 public function configure(): static12 {13 return $this->afterMaking(function (User $user) {14 // ...15 })->afterCreating(function (User $user) {16 // ...17 });18 }19 20 // ...21}
特定の状態に固有の追加タスクを実行するために、状態メソッド内でファクトリコールバックを登録することもできます。
1use App\Models\User; 2use Illuminate\Database\Eloquent\Factories\Factory; 3 4/** 5 * Indicate that the user is suspended. 6 */ 7public function suspended(): Factory 8{ 9 return $this->state(function (array $attributes) {10 return [11 'account_status' => 'suspended',12 ];13 })->afterMaking(function (User $user) {14 // ...15 })->afterCreating(function (User $user) {16 // ...17 });18}
ファクトリを使用したモデルの作成
モデルのインスタンス化
ファクトリを定義したら、Illuminate\Database\Eloquent\Factories\HasFactoryトレイトによってモデルに提供される静的なfactoryメソッドを使用して、そのモデルのファクトリインスタンスを生成できます。モデル作成の例をいくつか見てみましょう。まず、makeメソッドを使用して、データベースに永続化せずにモデルを作成します。
1use App\Models\User;2 3$user = User::factory()->make();
countメソッドを使用して、多数のモデルのコレクションを作成できます。
1$users = User::factory()->count(3)->make();
状態の適用
モデルに状態を適用することもできます。モデルに複数の状態変換を適用したい場合は、状態変換メソッドを直接呼び出すだけです。
1$users = User::factory()->count(5)->suspended()->make();
属性のオーバーライド
モデルのデフォルト値の一部を上書きしたい場合は、makeメソッドに値の配列を渡すことができます。指定された属性のみが置き換えられ、残りの属性はファクトリで指定されたデフォルト値のままになります。
1$user = User::factory()->make([2 'name' => 'Abigail Otwell',3]);
または、ファクトリインスタンスで直接stateメソッドを呼び出して、インラインの状態変換を実行することもできます。
1$user = User::factory()->state([2 'name' => 'Abigail Otwell',3])->make();
マスアサインメント保護は、ファクトリを使用してモデルを作成する際には自動的に無効になります。
モデルの永続化
createメソッドは、モデルインスタンスを生成し、Eloquentのsaveメソッドを使用してデータベースに永続化します。
1use App\Models\User;2 3// Create a single App\Models\User instance...4$user = User::factory()->create();5 6// Create three App\Models\User instances...7$users = User::factory()->count(3)->create();
createメソッドに属性の配列を渡すことで、ファクトリのデフォルトのモデル属性を上書きできます。
1$user = User::factory()->create([2 'name' => 'Abigail',3]);
シーケンス
作成されるモデルごとに、特定のモデル属性の値を交互に変更したい場合があります。これは、状態変換をシーケンスとして定義することで実現できます。たとえば、作成されるユーザーごとにadminカラムの値をYとNで交互に設定したい場合があります。
1use App\Models\User; 2use Illuminate\Database\Eloquent\Factories\Sequence; 3 4$users = User::factory() 5 ->count(10) 6 ->state(new Sequence( 7 ['admin' => 'Y'], 8 ['admin' => 'N'], 9 ))10 ->create();
この例では、5人のユーザーがadmin値Yで作成され、5人のユーザーがadmin値Nで作成されます。
必要であれば、シーケンス値としてクロージャを含めることができます。このクロージャは、シーケンスが新しい値を必要とするたびに呼び出されます。
1use Illuminate\Database\Eloquent\Factories\Sequence;2 3$users = User::factory()4 ->count(10)5 ->state(new Sequence(6 fn (Sequence $sequence) => ['role' => UserRoles::all()->random()],7 ))8 ->create();
シーケンスクロージャ内では、クロージャに注入されるシーケンスインスタンスの$indexまたは$countプロパティにアクセスできます。$indexプロパティには、それまでに行われたシーケンスの反復回数が含まれ、$countプロパティには、シーケンスが呼び出される合計回数が含まれます。
1$users = User::factory()2 ->count(10)3 ->sequence(fn (Sequence $sequence) => ['name' => 'Name '.$sequence->index])4 ->create();
便宜上、sequenceメソッドを使用してシーケンスを適用することもできます。これは内部でstateメソッドを呼び出すだけです。sequenceメソッドは、クロージャまたはシーケンス化された属性の配列を受け入れます。
1$users = User::factory()2 ->count(2)3 ->sequence(4 ['name' => 'First User'],5 ['name' => 'Second User'],6 )7 ->create();
ファクトリのリレーション
Has Manyリレーション
次に、Laravelの流れるようなファクトリメソッドを使用してEloquentモデルのリレーションを構築する方法を探ってみましょう。まず、アプリケーションにApp\Models\UserモデルとApp\Models\Postモデルがあると仮定します。また、UserモデルがPostとのhasManyリレーションを定義していると仮定します。Laravelのファクトリが提供するhasメソッドを使用して、3つの投稿を持つユーザーを作成できます。hasメソッドはファクトリインスタンスを受け入れます。
1use App\Models\Post;2use App\Models\User;3 4$user = User::factory()5 ->has(Post::factory()->count(3))6 ->create();
規約により、hasメソッドにPostモデルを渡すと、LaravelはUserモデルにリレーションを定義するpostsメソッドがなければならないと想定します。必要であれば、操作したいリレーションの名前を明示的に指定できます。
1$user = User::factory()2 ->has(Post::factory()->count(3), 'posts')3 ->create();
もちろん、関連モデルに対して状態操作を実行できます。さらに、状態変更が親モデルへのアクセスを必要とする場合は、クロージャベースの状態変換を渡すことができます。
1$user = User::factory()2 ->has(3 Post::factory()4 ->count(3)5 ->state(function (array $attributes, User $user) {6 return ['user_type' => $user->type];7 })8 )9 ->create();
マジックメソッドの使用
便宜上、Laravelのマジックファクトリリレーションメソッドを使用してリレーションを構築できます。たとえば、次の例では、規約を使用して、関連モデルがUserモデルのpostsリレーションメソッドを介して作成されるべきであると判断します。
1$user = User::factory()2 ->hasPosts(3)3 ->create();
マジックメソッドを使用してファクトリリレーションを作成する場合、関連モデルで上書きする属性の配列を渡すことができます。
1$user = User::factory()2 ->hasPosts(3, [3 'published' => false,4 ])5 ->create();
状態変更が親モデルへのアクセスを必要とする場合は、クロージャベースの状態変換を提供できます。
1$user = User::factory()2 ->hasPosts(3, function (array $attributes, User $user) {3 return ['user_type' => $user->type];4 })5 ->create();
Belongs Toリレーション
ファクトリを使用して「has many」リレーションを構築する方法を探ったので、次はそのリレーションの逆を探ってみましょう。forメソッドを使用して、ファクトリで作成されたモデルが属する親モデルを定義できます。たとえば、1人のユーザーに属する3つのApp\Models\Postモデルインスタンスを作成できます。
1use App\Models\Post;2use App\Models\User;3 4$posts = Post::factory()5 ->count(3)6 ->for(User::factory()->state([7 'name' => 'Jessica Archer',8 ]))9 ->create();
作成中のモデルに関連付けるべき親モデルのインスタンスが既にある場合は、そのモデルインスタンスをforメソッドに渡すことができます。
1$user = User::factory()->create();2 3$posts = Post::factory()4 ->count(3)5 ->for($user)6 ->create();
マジックメソッドの使用
便宜上、Laravelのマジックファクトリリレーションメソッドを使用して「belongs to」リレーションを定義できます。たとえば、次の例では、規約を使用して、3つの投稿がPostモデルのuserリレーションに属するべきであると判断します。
1$posts = Post::factory()2 ->count(3)3 ->forUser([4 'name' => 'Jessica Archer',5 ])6 ->create();
Many to Manyリレーション
has manyリレーションと同様に、「many to many」リレーションもhasメソッドを使用して作成できます。
1use App\Models\Role;2use App\Models\User;3 4$user = User::factory()5 ->has(Role::factory()->count(3))6 ->create();
ピボットテーブルの属性
モデルをリンクするピボット/中間テーブルに設定する必要がある属性を定義する必要がある場合は、hasAttachedメソッドを使用できます。このメソッドは、2番目の引数としてピボットテーブルの属性名と値の配列を受け入れます。
1use App\Models\Role;2use App\Models\User;3 4$user = User::factory()5 ->hasAttached(6 Role::factory()->count(3),7 ['active' => true]8 )9 ->create();
状態変更が関連モデルへのアクセスを必要とする場合は、クロージャベースの状態変換を提供できます。
1$user = User::factory() 2 ->hasAttached( 3 Role::factory() 4 ->count(3) 5 ->state(function (array $attributes, User $user) { 6 return ['name' => $user->name.' Role']; 7 }), 8 ['active' => true] 9 )10 ->create();
作成中のモデルに添付したいモデルインスタンスが既にある場合は、そのモデルインスタンスをhasAttachedメソッドに渡すことができます。この例では、同じ3つのロールが3人のユーザーすべてに添付されます。
1$roles = Role::factory()->count(3)->create();2 3$user = User::factory()4 ->count(3)5 ->hasAttached($roles, ['active' => true])6 ->create();
マジックメソッドの使用
便宜上、Laravelのマジックファクトリリレーションメソッドを使用してmany-to-manyリレーションを定義できます。たとえば、次の例では、規約を使用して、関連モデルがUserモデルのrolesリレーションメソッドを介して作成されるべきであると判断します。
1$user = User::factory()2 ->hasRoles(1, [3 'name' => 'Editor'4 ])5 ->create();
ポリモーフィックリレーション
ポリモーフィックリレーションもファクトリを使用して作成できます。ポリモーフィックな「morph many」リレーションは、典型的な「has many」リレーションと同じ方法で作成されます。たとえば、App\Models\PostモデルがApp\Models\CommentモデルとのmorphManyリレーションを持っている場合です。
1use App\Models\Post;2 3$post = Post::factory()->hasComments(3)->create();
Morph Toリレーション
morphToリレーションの作成にマジックメソッドは使用できません。代わりに、forメソッドを直接使用し、リレーションの名前を明示的に指定する必要があります。たとえば、CommentモデルにmorphToリレーションを定義するcommentableメソッドがあるとします。この状況では、forメソッドを直接使用して、1つの投稿に属する3つのコメントを作成できます。
1$comments = Comment::factory()->count(3)->for(2 Post::factory(), 'commentable'3)->create();
ポリモーフィックMany to Manyリレーション
ポリモーフィックな「many to many」(morphToMany / morphedByMany)リレーションは、非ポリモーフィックな「many to many」リレーションと同じように作成できます。
1use App\Models\Tag;2use App\Models\Video;3 4$videos = Video::factory()5 ->hasAttached(6 Tag::factory()->count(3),7 ['public' => true]8 )9 ->create();
もちろん、マジックhasメソッドを使用してポリモーフィックな「many to many」リレーションを作成することもできます。
1$videos = Video::factory()2 ->hasTags(3, ['public' => true])3 ->create();
ファクトリ内でのリレーション定義
モデルファクトリ内でリレーションを定義するには、通常、新しいファクトリインスタンスをリレーションの外部キーに割り当てます。これは通常、belongsToやmorphToなどの「逆」のリレーションに対して行われます。たとえば、投稿を作成する際に新しいユーザーを作成したい場合は、次のようにします。
1use App\Models\User; 2 3/** 4 * Define the model's default state. 5 * 6 * @return array<string, mixed> 7 */ 8public function definition(): array 9{10 return [11 'user_id' => User::factory(),12 'title' => fake()->title(),13 'content' => fake()->paragraph(),14 ];15}
リレーションのカラムがそれを定義するファクトリに依存する場合、属性にクロージャを割り当てることができます。クロージャは、ファクトリの評価済み属性配列を受け取ります。
1/** 2 * Define the model's default state. 3 * 4 * @return array<string, mixed> 5 */ 6public function definition(): array 7{ 8 return [ 9 'user_id' => User::factory(),10 'user_type' => function (array $attributes) {11 return User::find($attributes['user_id'])->type;12 },13 'title' => fake()->title(),14 'content' => fake()->paragraph(),15 ];16}
リレーションのための既存モデルの再利用
あるモデルと共通のリレーションを持つ複数のモデルがある場合、recycleメソッドを使用して、ファクトリによって作成されるすべてのリレーションに対して関連モデルの単一インスタンスが再利用されるようにすることができます。
たとえば、Airline、Flight、Ticketモデルがあり、チケットは航空会社とフライトに属し、フライトも航空会社に属するとします。チケットを作成する際、チケットとフライトの両方で同じ航空会社を使用したいでしょうから、recycleメソッドに航空会社のインスタンスを渡すことができます。
1Ticket::factory()2 ->recycle(Airline::factory()->create())3 ->create();
recycleメソッドは、共通のユーザーやチームに属するモデルがある場合に特に役立ちます。
recycleメソッドは、既存のモデルのコレクションも受け入れます。コレクションがrecycleメソッドに渡されると、ファクトリがそのタイプのモデルを必要とするときに、コレクションからランダムなモデルが選択されます。
1Ticket::factory()2 ->recycle($airlines)3 ->create();