🔋

Laravel Factory の新メソッド recycle を使ってみる

2022/11/04に公開

前書き

小ネタにはなりますが、Laravel 9.30.1 にて、ダミーデータを作る際などに利用する Factory に recycle() というメソッドが加わりましたので、これを利用して少し恩恵を味わってみたいと思います。

ちなみ今回こちらで見る機能は、Laravel 9.34 以上から実現可能です。(詳しくは下で)

本題

現状の困り所は何かと言うと、A user has many posts (1ユーザーが多数のブログ記事を所有している)という関係の時に、普通な感じでダミーデータを作成すると、posts の所有者が、連番1から綺麗に並んでしまうんですね。こんな感じです。


posts テーブルの中身

いかにもダミーデータという感じで、少しやる気が失われます。(笑

まぁ、考え方によっては、綺麗に並んでいる方が、何かテストし易いとかあるかも知れませんが、それを言ってはブログのネタが切れてしまいますので、今回は recycle() 機能を使って、これをランダム化してより本物っぽいデータにしたいと思います。という事で、改めて具体的にコードで見ていきます。

コマンドで、Post モデルと同時にマイグレーションファイル、ファクトリファイルを作成します。

php artisan make:model Post -mf
マイグレーションファイル
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignIdFor(User::class);
            $table->string('title');
            $table->longText('body');
            $table->timestamps();
        });
    }
PostFactory
    public function definition()
    {
        return [
            'user_id' => User::factory(),
            'title' => $this->faker->realText(10),
            'body' => $this->faker->realText(10),
        ];
    }

Post モデルには以下を追加。

Post
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

で、DatabaseSeeder に以下のように書きます。
ユーザーを3人作って、その各々に3件の記事を所有させる形です。

DatabaseSeeder
    public function run()
    {
        User::factory(3)->create()->each(function (User $user) {
            Post::factory(3)->for($user)->create();
        });
    }

これで、マイグレーション&シーディングを実行すると、

php artisan migrate:fresh --seed

先程の画像と同じですが、いかにもテストっぽい感じの並びになります。

posts テーブルの中身

ちょっとイケてないですよね?

では気を改めて、試しに以下のように書いてみると、、、

DatabaseSeeder
    public function run()
    {
        User::factory(3)->create();
        Post::factory(10)->create(['user_id' => random_int(1, 3)]);
    }

再度、php artisan migrate:fresh --seed してみると、、

posts テーブルの中身

あぁ、切ない事になりました…💦
random_int(1, 3) は確かに走ったのですが、1回走っただけで、その1回の結果が全ての Post 10件分に適用されています。

こうなると、for() とか使って、1件1件ぐるぐる回して処理するしかないのか?と思ったりしますが、正直あまり地味な事はしたくありません。

そこで、今回の recycle() メソッドを使うと、こんな感じでいけます。

DatabaseSeeder
    public function run()
    {
        $users = User::factory(3)->create();

        Post::factory(10)->recycle($users)->create();
    }

すると今度は、こんな感じになります。

posts テーブルの中身

おぉ、より自然な感じで本物っぽくなりましたね。

上記は、下記みたいに書くのもいいかも知れませんね。

DatabaseSeeder
    public function run()
    {
        Post::factory(10)->recycle(
            User::factory(3)->create()
        )->create();
    }

recycle メソッドについて

この recycle メソッドですが、元は Ver.9.30.1 の jessarcher さんによるもので、今回の例とは少し違った使い方を意図したものでした。日本語ドキュメントにも載っていますので、興味のある方は、そちら参照して下さい。

ですが、その後、Ver.9.34 にて少し拡張され「モデルを作成する際、recycle() に渡された複数モデルをランダムに利用する」となりましたので、今回はその拡張部分を使った感じになります。

https://github.com/laravel/framework/pull/44107

https://github.com/laravel/framework/pull/44328

雑感

ちなみに、元のプルリクでは、using() と言う普通な感じのメソッド名でしたが、最後は、Taylor 氏によって recycle() と言うインパクトあり過ぎなメソッド名に変更されました😄

もし良ければ、今回のコード、リサイクルして下さい ♻️

間違い等見つけましたらコメント下さい🙇‍♂️

Discussion