環境
PHP 8.1
Laravel 9.19
テーブル構造
最初にマイグレーションファイルを見ていきましょう。
usersはデフォルトのまま。
Schema::create('users', static function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); });
usersテーブルと一対多で関連付けされているpostsテーブル。
Schema::create('posts', static function (Blueprint $table) { $table->id(); $table->string('title', 255); $table->text('body'); $table->foreignId('user_id'); $table->timestamps(); });
postsテーブルと多対多の関連付けされているtagsテーブル。
Schema::create('tags', static function (Blueprint $table) { $table->id(); $table->string('slug', 100); $table->string('name', 100); $table->timestamps(); });
postsテーブルとtagsテーブルの中間テーブルになるpost_tag
中間テーブルにもメモ的なカラムがあります。
Schema::create('post_tag', static function (Blueprint $table) { $table->id(); $table->foreignId('post_id'); $table->foreignId('tag_id'); $table->string('memo')->nullable(); });
こんな感じでそれぞれのテーブルにFactoryでデータを作成していきます。
モデル
Factoryを使用するにはモデルが必要です。
Factoryを使用するモデルにはHasFactoryをuseする必要があります。
makeコマンドで作成しているならデフォルトでuseされています。
またリレーションの設定もこの段階で完了しておく必要があります。
app/Models/User.php
use Illuminate\Database\Eloquent\Factories\HasFactory; class User extends Authenticatable implements MustVerifyEmail { use HasApiTokens, HasFactory, Notifiable; public function posts(): HasMany { return $this->hasMany(Post::class); } }
app/Models/Post.php
class Post extends Model { use HasFactory public function user(): BelongsTo { return $this->belongsTo(User::class); } public function tags(): BelongsToMany { return $this->belongsToMany(Tag::class); } }
app/Models/Tag.php
class Tag extends Model { use HasFactory; public function posts(): BelongsToMany { return $this->belongsToMany(Post::class); } }
ファクトリ
いよいよファクトリファイルを作りますが、ファクトリもそんなに悩むところはありません。
UserFactory.phpは基本デフォルトのままですが、created_at,updated_atを入力しないとコマンドを実行した日時になるので、もしランダムに入れたいときはdateTimeBetween
を使うといいかもしれないです。
database/factories/UserFactory.php
public function definition() { $date = $this->faker->dateTimeBetween('-1year'); return [ 'name' => $this->faker->unique()->userName(), 'email' => $this->faker->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), 'created_at' => $date, 'updated_at' => $date, ]; }
次はusersテーブルと関連を持っているpostsです。
user_idにUser::factory()
を指定します。
database/factories/PostFactory.php
public function definition() { $date = $this->faker->dateTimeBetween('-1year'); return [ 'title' => $this->faker->realText(random_int(30, 250)), 'body' => $this->faker->realText(random_int(200, 800)), 'user_id' => User::factory(), 'created_at' => $date, 'updated_at' => $date, ]; }
最後にtagsテーブル。中間テーブルはモデルと同様に必要ありません。
database/factories/TagFactory.php
public function definition() { return [ 'slug' => $this->faker->unique()->slug(), 'name' => $this->faker->unique()->word(), ]; }
これで準備は完了です。
シーダファイルの作成
ここがらが本題です。
作成したファクトリでどのようなデータが作成されるか確認していきましょう。
DatabaseSeeder.phpにfactoryメソッドを記述していきます。
最初にPostモデルのfactoryを単独で実行してみます。
database/seeders/DatabaseSeeder.php
public function run() { Post::factory(10)->create(); }
初回はmigrate実行時にseedオプションを付与して実行します。
$ php artisan migrate:fresh --seed
すでにテーブルがある場合はdb:seedを実行します。
$ php artisan db:seed
ファクトリファイルでUser::factory()
を指定したので、postsと同じ数だけusersにもレコードが作成され関連付けされたと思います。
ただこれだと同じ数のデータなので一対一の関係ですね。
一対多のデータ作成
一であるUserを先に作成します。
User::factory(3) ->hasPosts(3) ->create();
ただこれだとUserが持っているPostの数が固定になります。
ランダムな数で関連付けしたい場合はUserデータを作成した後、Postのfactoryでrecycle
を実行します。
$users = User::factory(5)->create(); Post::factory(50)->recycle($users)->create();
これでpostsテーブルのuser_idカラムに、あらかじめ作成したusersのランダムなidが入り一対多のデータになります。
多対多のデータ作英
多対多はいろいろパターンがあるのですが、最初はPostレコードの数だけTagも作成する方法です。
ひとつのPostに対して3つずつTagを作るのは下記のようにします。
Post::factory(50) ->hasTags(3) ->create();
次にあらかじめTagを作って関連付けする方法です。
$tags = Tag::factory(3)->create(); Post::factory(50) ->hasAttached($tags) ->create();
中間テーブルのカラムに入れる場合。
Post::factory(50) ->hasAttached( $tags, fn () => ['memo' => fake()->realText(random_int(10, 30))] ) ->create();
関連付けるTagの数もランダムにしたい場合。
Post::factory(50) ->create() ->each(fn ($post) => $post ->tags() ->attach($tags->random(random_int(1, 3))) );
最後に中間テーブルのカラムもランダムで入れたい場合。
Post::factory(50) ->create() ->each(fn ($post) => array_map(static fn ($value) => $post ->tags() ->attach( $tags->random(), ['memo' => fake()->realText(random_int(30, 50))] ), range(1, random_int(1, 3))) );
もっとスマートな方法がありそうだけどわかりませんでした。以上。