WEBOPIXEL

LaravelのModel Factoryでリレーション関係のダミーデータを作る

Laravelロゴ

Posted: 2023.01.31 / Category: PHP / Tag: 

LaravelにはFactoryというデータベースにダミーデータを投入する機能があります。
単一のテーブルにデータを入れるだけなら簡単ですが、リレーション構造を持った複数のテーブルを扱うときにはまったのでメモしておきます。

Sponsored Link

環境

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

もっとスマートな方法がありそうだけどわかりませんでした。以上。