🚀

Laravel8以降のModelFactoryをまとめなおしたもの。

2022/04/13に公開

だいたいチートシートです。

環境

Laravel 8以降を対象として考えています。
9.xのソース見ながら書いたのでもしかしたら対応してないものがあるかも

Factoryの生成

php artisan make:factory PostFactory

ModelFactoryの基本形

ModelFactoryのクラスは以下のような記述になる。

UserFactory
class UserFactory extends Factory
{
    public function definition(): array
    {
        // 対象のモデルを作る際に必要な情報を配列で返す
        return [
            'name' => $this->faker->name(),
            'email' => $this->faker->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => Hash::make('password'), // 実際のときはすでにハッシュ化したやつを使ったほうがよろし
            'remember_token' => Str::random(10),
        ];
    }
}

ModelFactoryとModelの対応付け

Factory生成後はIlluminate\Database\Eloquent\Factories\HasFactoryトレイトを対象となるモデルクラスに読み込む。

use Illuminate\Database\Eloquent\Factories\HasFactory;

class Post extends Model
{
    use HasFactory;

Factoryの名前のsuffix(接尾辞)がFactoryで終わらない場合、モデルのnewFactoryメソッドを上書きする必要がある。バージョンによってはModelの階層をちゃんと認識しないものもあるため、その際にも上書きが必要(このトレイトを拡張するのも手)。

Postモデル
protected static function newFactory()
{
    return PostFactory::new();
}

Model側も、Factoryの名前から自動的にModelを把握しようとしますが、できない場合はModelFactoryにmodelプロパティを定義します。

PostFactory
class PostFactory
{
    protected $model = Post::class;
    //
}

Factoryを使う

よくあるやつ

テストコード内で
$user = User::factory()->make(); // インスタンスを作るだけ
$user = User::factory()->create(); // DBに追加する

$users = User::factory()->count(3)->create(); // 3個作ってCollectionを返す
$users = User::factory(3)->create(); // 上と同じ意味

// 気をつける点(nullと数値ありでは挙動が異なる)
$user = User::factory()->create(); // モデルのインスタンスを返す
$users = User::factory(1)->create(); // コレクションを返す

$user = User::factory()->state(['name' => 'ほげほげ'])->create(); // 状態を別のものに変えて作る
$user = User::factory()->set('name', 'ほげほげ')->create(); // 上と同じ意味
$user = User::factory()->create(['name' => 'ほげほげ']); // 上と同じ意味

ほぼ使わないやつ

// 順番に状態を変えて作る(Y,N,Y,N...と作られていく)
use Illuminate\Database\Eloquent\Factories\Sequence;
$users = User::factory(10)->create(new Sequence(
    ['admin' => 'Y'],
    ['admin' => 'N'],
));

// こっちのほうがわかりやすいかも
$users = User::factory(10)->sequence(
    ['admin' => 'Y'],
    ['admin' => 'N'],
)->create();

// 以下のようにコールバックを指定することも可能
new Sequence(fn ($sequence) => ['role' => UserRoles::all()->random()],);
// $sequenceにはindexプロパティとcountプロパティにアクセスすることが可能

リレーションする

親→子や親→中間→子と作る場合

HasManyManyToMany, BelongsToMany, MorphMany, MorphToMany, MorphedByMany の場合はだいたいこっちで完結する。

$user = User::factory()->has(Post::factory(3))->create();
// リレーション設定しているメソッドを明示的に指定する
$user = User::factory()->has(Post::factory(3), 'posts')->create();
// 作る際に親モデルの情報が必要な場合
$user = User::factory()->has(
    Post::factory(3)
        ->state(
        function(array $attributes, User $user) {
            return ['user_type' => $user->type];
        })
)->create();

マジックメソッドを利用する

// マジックメソッドでのアクセスも可能(Postsの部分をcamelizeしてメソッドとして扱うよ)
$user = User::factory()->hasPosts(3)->create(); 
// 属性変更したい場合
$user = User::factory()->hasPosts(3, ['published' => 'true'])->create();
// 実はこれも属性変更として動く(第1引数が数字かどうかでcountに渡すかを決めている、versionによっては動かないかも)
$user = User::factory()->hasPosts(['published' => 'true'])->create(); 

中間テーブルの属性を渡したいとき

$user = User::factory()->hasAttached(
    Role::factory(3),
    ['active' => true]
)->create();
// すでに作ってあっても大丈夫
$user = User::factory()->hasAttached(
    Role::factory(3)->create(),
    ['active' => true]
)->create();

子→親のようなBelongsToの場合

BelongsToの場合はこっち。

$posts = Post::factory(3)->for(User::factory())->create();
// すでに作られたモデルインスタンスを渡すことも出来る
$posts = Post::factory(3)->for(User::factory()->create())->create();
// hasのときと同様にマジックメソッドでのアクセスが可能、ただし最初の引数からstateメソッドに渡すやつになる
$posts = Post::factory(3)->forUser(['name' => 'ふがふが'])->create();

morphToな場合

MorphToだけはマジックメソッドを使えない。
ただ記述的にはBlongsToのような逆定義での方法になる。個々にcreateしちゃっても良いかもしれない。

// morphToな関係の場合、第2引数に対応するメソッド名を書く
$comments = Comment::factory(3)->for(Post::factory(), 'commentable')->create();

ModelFactory内での設定

状態のメソッド

ModelFactory内のメソッドとして定義
public function suspended()
{
    return $this->state(function (array $attributes) {
        return [
            'account_status' => 'suspended',
        ];
    });
}
$user = User::factory()->suspended()->create();

作成後のコールバック実行

ModelFactory内にconfigureメソッドを定義
public function configure()
{
    return $this->afterMaking(function (User $user) {
        // makingはcreateでも呼ばれるよ
    })->afterCreating(function (User $user) {
        // こっちはcreate時だけ、実行自体はmakingのほうが先
    });
}

リレーション定義

ModelFactory内でのリレーション定義は基本belongsTomorphToのような逆関係用として存在。
7系まではこっちのほうが記述例としては多かったはず

public function definition()
{
    return [
        'user_id' => User::factory(),
	// もしもこのdefinitionで定義したデータを使って設定したい場合
	'user_type' => function (array $attributes) {
            return User::find($attributes['user_id'])->type;
        },
	'title' => $this->faker->title(),
    ];
}

Discussion