🚀
Laravel8以降のModelFactoryをまとめなおしたもの。
だいたいチートシートです。
環境
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プロパティにアクセスすることが可能
リレーションする
親→子や親→中間→子と作る場合
HasMany
、ManyToMany
, 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内でのリレーション定義は基本belongsTo
やmorphTo
のような逆関係用として存在。
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