はじめに

アプリケーションをテストしたり、データベースにデータを投入したりする際に、データベースにいくつかのレコードを挿入する必要があるかもしれません。各カラムの値を手動で指定する代わりに、Laravelではモデルファクトリを使用して、各Eloquentモデルのデフォルト属性のセットを定義することができます。

ファクトリの書き方の例を見たい場合は、アプリケーション内のdatabase/factories/UserFactory.phpファイルを確認してください。このファクトリはすべての新しいLaravelアプリケーションに含まれており、以下のファクトリ定義が含まれています:

  1. namespace Database\Factories;
  2. use Illuminate\Database\Eloquent\Factories\Factory;
  3. use Illuminate\Support\Facades\Hash;
  4. use Illuminate\Support\Str;
  5. /**
  6. * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
  7. */
  8. class UserFactory extends Factory
  9. {
  10. /**
  11. * The current password being used by the factory.
  12. */
  13. protected static ?string $password;
  14. /**
  15. * Define the model's default state.
  16. *
  17. * @return array<string, mixed>
  18. */
  19. public function definition(): array
  20. {
  21. return [
  22. 'name' => fake()->name(),
  23. 'email' => fake()->unique()->safeEmail(),
  24. 'email_verified_at' => now(),
  25. 'password' => static::$password ??= Hash::make('password'),
  26. 'remember_token' => Str::random(10),
  27. ];
  28. }
  29. /**
  30. * Indicate that the model's email address should be unverified.
  31. */
  32. public function unverified(): static
  33. {
  34. return $this->state(fn (array $attributes) => [
  35. 'email_verified_at' => null,
  36. ]);
  37. }
  38. }

ご覧のとおり、最も基本的な形では、ファクトリはLaravelの基本ファクトリクラスを拡張し、definitionメソッドを定義するクラスです。definitionメソッドは、ファクトリを使用してモデルを作成する際に適用されるべきデフォルトの属性値のセットを返します。

fakeヘルパーを介して、ファクトリはFaker PHPライブラリにアクセスでき、テストやデータ投入のためにさまざまな種類のランダムデータを便利に生成できます。

アプリケーションのFakerロケールは、faker_localeオプションをconfig/app.php設定ファイルで更新することで変更できます。

モデルファクトリの定義

ファクトリの生成

ファクトリを作成するには、make:factory Artisanコマンドを実行します:

  1. php artisan make:factory PostFactory

新しいファクトリクラスは、database/factoriesディレクトリに配置されます。

モデルとファクトリの発見規約

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

  1. ``````php
  2. use Database\Factories\Administration\FlightFactory;
  3. /**
  4. * Create a new factory instance for the model.
  5. */
  6. protected static function newFactory()
  7. {
  8. return FlightFactory::new();
  9. }
  10. `

次に、対応するファクトリにmodelプロパティを定義します:

  1. use App\Administration\Flight;
  2. use Illuminate\Database\Eloquent\Factories\Factory;
  3. class FlightFactory extends Factory
  4. {
  5. /**
  6. * The name of the factory's corresponding model.
  7. *
  8. * @var class-string<\Illuminate\Database\Eloquent\Model>
  9. */
  10. protected $model = Flight::class;
  11. }

ファクトリの状態

状態操作メソッドを使用すると、モデルファクトリに適用できる離散的な変更を任意の組み合わせで定義できます。たとえば、Database\Factories\UserFactoryファクトリには、デフォルトの属性値の1つを変更するsuspended状態メソッドが含まれているかもしれません。

状態変換メソッドは通常、Laravelの基本ファクトリクラスによって提供されるstateメソッドを呼び出します。stateメソッドは、ファクトリのために定義された生の属性の配列を受け取り、変更する属性の配列を返すクロージャを受け入れます:

  1. use Illuminate\Database\Eloquent\Factories\Factory;
  2. /**
  3. * Indicate that the user is suspended.
  4. */
  5. public function suspended(): Factory
  6. {
  7. return $this->state(function (array $attributes) {
  8. return [
  9. 'account_status' => 'suspended',
  10. ];
  11. });
  12. }

「削除済み」状態

Eloquentモデルがソフト削除できる場合、作成されたモデルがすでに「ソフト削除」されていることを示すために、組み込みのtrashed状態メソッドを呼び出すことができます。trashed状態を手動で定義する必要はありません。これはすべてのファクトリで自動的に利用可能です:

  1. use App\Models\User;
  2. $user = User::factory()->trashed()->create();

ファクトリコールバック

ファクトリコールバックは、afterMakingおよびafterCreatingメソッドを使用して登録され、モデルを作成または生成した後に追加のタスクを実行できます。これらのコールバックは、ファクトリクラスにconfigureメソッドを定義することで登録する必要があります。このメソッドは、ファクトリがインスタンス化されるときにLaravelによって自動的に呼び出されます:

  1. namespace Database\Factories;
  2. use App\Models\User;
  3. use Illuminate\Database\Eloquent\Factories\Factory;
  4. class UserFactory extends Factory
  5. {
  6. /**
  7. * Configure the model factory.
  8. */
  9. public function configure(): static
  10. {
  11. return $this->afterMaking(function (User $user) {
  12. // ...
  13. })->afterCreating(function (User $user) {
  14. // ...
  15. });
  16. }
  17. // ...
  18. }

状態メソッド内でファクトリコールバックを登録して、特定の状態に固有の追加タスクを実行することもできます:

  1. use App\Models\User;
  2. use Illuminate\Database\Eloquent\Factories\Factory;
  3. /**
  4. * Indicate that the user is suspended.
  5. */
  6. public function suspended(): Factory
  7. {
  8. return $this->state(function (array $attributes) {
  9. return [
  10. 'account_status' => 'suspended',
  11. ];
  12. })->afterMaking(function (User $user) {
  13. // ...
  14. })->afterCreating(function (User $user) {
  15. // ...
  16. });
  17. }

ファクトリを使用したモデルの作成

モデルのインスタンス化

ファクトリを定義したら、Illuminate\Database\Eloquent\Factories\HasFactoryトレイトによってモデルに提供される静的factoryメソッドを使用して、そのモデルのファクトリインスタンスをインスタンス化できます。いくつかのモデルを作成する例を見てみましょう。まず、makeメソッドを使用して、モデルをデータベースに永続化せずに作成します:

  1. use App\Models\User;
  2. $user = User::factory()->make();
  1. ``````php
  2. $users = User::factory()->count(3)->make();
  3. `

状態の適用

モデルに任意のstateを適用することもできます。モデルに複数の状態変換を適用したい場合は、状態変換メソッドを直接呼び出すだけです:

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

マスアサインメント保護は、ファクトリを使用してモデルを作成する際に自動的に無効になります。

モデルの永続化

  1. ``````php
  2. use App\Models\User;
  3. // Create a single App\Models\User instance...
  4. $user = User::factory()->create();
  5. // Create three App\Models\User instances...
  6. $users = User::factory()->count(3)->create();
  7. `

ファクトリのデフォルトモデル属性をオーバーライドするには、createメソッドに属性の配列を渡します:

  1. $user = User::factory()->create([
  2. 'name' => 'Abigail',
  3. ]);

シーケンス

時には、作成された各モデルの特定のモデル属性の値を交互に変更したい場合があります。これをシーケンスとして状態変換を定義することで実現できます。たとえば、adminカラムの値をYNの間で交互に変更したい場合があります:

  1. use App\Models\User;
  2. use Illuminate\Database\Eloquent\Factories\Sequence;
  3. $users = User::factory()
  4. ->count(10)
  5. ->state(new Sequence(
  6. ['admin' => 'Y'],
  7. ['admin' => 'N'],
  8. ))
  9. ->create();

この例では、5人のユーザーがadminの値がYのもので作成され、5人のユーザーがadminの値がNのもので作成されます。

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

  1. use Illuminate\Database\Eloquent\Factories\Sequence;
  2. $users = User::factory()
  3. ->count(10)
  4. ->state(new Sequence(
  5. fn (Sequence $sequence) => ['role' => UserRoles::all()->random()],
  6. ))
  7. ->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();

ファクトリの関係

多くの関係

次に、Laravelの流暢なファクトリメソッドを使用してEloquentモデルの関係を構築する方法を探ります。まず、アプリケーションにApp\Models\UserモデルとApp\Models\Postモデルがあると仮定します。また、UserモデルがhasMany関係をPostと定義していると仮定します。Laravelのファクトリによって提供されるhasメソッドを使用して、3つの投稿を持つユーザーを作成できます。hasメソッドはファクトリインスタンスを受け入れます:

  1. use App\Models\Post;
  2. use App\Models\User;
  3. $user = User::factory()
  4. ->has(Post::factory()->count(3))
  5. ->create();

規約により、Postモデルをhasメソッドに渡すと、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のマジックファクトリ関係メソッドを使用して関係を構築できます。たとえば、次の例では、関連モデルが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();

属する関係

ファクトリを使用して「多くの関係」を構築する方法を探ったので、関係の逆を探ります。forメソッドを使用して、ファクトリが作成したモデルが属する親モデルを定義できます。たとえば、単一のユーザーに属する3つのApp\Models\Postモデルインスタンスを作成できます:

  1. use App\Models\Post;
  2. use App\Models\User;
  3. $posts = Post::factory()
  4. ->count(3)
  5. ->for(User::factory()->state([
  6. 'name' => 'Jessica Archer',
  7. ]))
  8. ->create();

作成しているモデルに関連付けるべき親モデルインスタンスがすでにある場合は、forメソッドにモデルインスタンスを渡すことができます:

  1. $user = User::factory()->create();
  2. $posts = Post::factory()
  3. ->count(3)
  4. ->for($user)
  5. ->create();

マジックメソッドの使用

便利なことに、Laravelのマジックファクトリ関係メソッドを使用して「属する」関係を定義できます。たとえば、次の例では、3つの投稿がuser関係に属するべきであることを規約に基づいて判断します:

  1. $posts = Post::factory()
  2. ->count(3)
  3. ->forUser([
  4. 'name' => 'Jessica Archer',
  5. ])
  6. ->create();

多対多の関係

多くの関係と同様に、「多対多」の関係はhasメソッドを使用して作成できます:

  1. use App\Models\Role;
  2. use App\Models\User;
  3. $user = User::factory()
  4. ->has(Role::factory()->count(3))
  5. ->create();

ピボットテーブル属性

モデルをリンクするピボット/中間テーブルに設定する属性を定義する必要がある場合は、hasAttachedメソッドを使用できます。このメソッドは、ピボットテーブル属性名と値の配列を第2引数として受け入れます:

  1. use App\Models\Role;
  2. use App\Models\User;
  3. $user = User::factory()
  4. ->hasAttached(
  5. Role::factory()->count(3),
  6. ['active' => true]
  7. )
  8. ->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. $user = User::factory()
  3. ->count(3)
  4. ->hasAttached($roles, ['active' => true])
  5. ->create();

マジックメソッドの使用

便利なことに、Laravelのマジックファクトリ関係メソッドを使用して多対多の関係を定義できます。たとえば、次の例では、関連モデルがroles関係メソッドを使用してUserモデルで作成されるべきであることを規約に基づいて判断します:

  1. $user = User::factory()
  2. ->hasRoles(1, [
  3. 'name' => 'Editor'
  4. ])
  5. ->create();

ポリモーフィック関係

ポリモーフィック関係もファクトリを使用して作成できます。ポリモーフィック「モーフ多」は、通常の「多くの」関係と同じ方法で作成されます。たとえば、App\Models\PostモデルがmorphManyモデルとApp\Models\Commentモデルとの関係を持っている場合:

  1. use App\Models\Post;
  2. $post = Post::factory()->hasComments(3)->create();

モーフトゥ関係

マジックメソッドを使用してmorphTo関係を作成することはできません。代わりに、forメソッドを直接使用し、関係の名前を明示的に指定する必要があります。たとえば、Commentモデルがcommentableメソッドを持ち、morphTo関係を定義していると仮定します。この場合、forメソッドを直接使用して、単一の投稿に属する3つのコメントを作成できます:

  1. $comments = Comment::factory()->count(3)->for(
  2. Post::factory(), 'commentable'
  3. )->create();

ポリモーフィック多対多の関係

ポリモーフィック「多対多」(morphToMany / morphedByMany)の関係は、非ポリモーフィック「多対多」の関係と同じように作成できます:

  1. use App\Models\Tag;
  2. use App\Models\Video;
  3. $videos = Video::factory()
  4. ->hasAttached(
  5. Tag::factory()->count(3),
  6. ['public' => true]
  7. )
  8. ->create();

もちろん、マジックhasメソッドを使用してポリモーフィック「多対多」の関係を作成することもできます:

  1. $videos = Video::factory()
  2. ->hasTags(3, ['public' => true])
  3. ->create();

ファクトリ内での関係の定義

モデルファクトリ内で関係を定義するには、通常、関係の外部キーに新しいファクトリインスタンスを割り当てます。これは通常、belongsToおよびmorphToのような「逆」関係に対して行われます。たとえば、投稿を作成する際に新しいユーザーを作成したい場合は、次のようにします:

  1. use App\Models\User;
  2. /**
  3. * Define the model's default state.
  4. *
  5. * @return array<string, mixed>
  6. */
  7. public function definition(): array
  8. {
  9. return [
  10. 'user_id' => User::factory(),
  11. 'title' => fake()->title(),
  12. 'content' => fake()->paragraph(),
  13. ];
  14. }

関係のカラムがそれを定義するファクトリに依存している場合は、属性にクロージャを割り当てることができます。クロージャは、ファクトリの評価された属性配列を受け取ります:

  1. /**
  2. * Define the model's default state.
  3. *
  4. * @return array<string, mixed>
  5. */
  6. public 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メソッドを使用して、ファクトリによって作成されたすべての関係に対して関連モデルの単一インスタンスが再利用されることを保証できます。

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

  1. Ticket::factory()
  2. ->recycle(Airline::factory()->create())
  3. ->create();

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

  1. ``````php
  2. Ticket::factory()
  3. ->recycle($airlines)
  4. ->create();
  5. `