factoryを使ったダミーデータ作成方法の2回目です。前回は単一モデルを扱った内容でしたが、今回は「1対多」のリレーションデータを作成します。

前回はこちらから閲覧できます。

1対多のリレーション

店舗(Shop)と、店舗に複数の商品(Product)が属している1対多のリレーションです。商品テーブルは、店舗のプライマリキーであるshop_idを外部キーとして格納しています。

factoryの定義

factoryでダミーデータを作るにあたり、factoryにモデルの属性値を定義する必要があります。

まず、Shopに対して以下のように定義します。

class ShopFactory extends Factory
{
    public function definition()
    {
        return [
            'name' => $this->faker->name(),
        ];
    }
}

Product側も同様です。子は関連する親のid情報を持つ必要があるので、shop_idカラムも定義します。

class ProductFactory extends Factory
{
    public function definition()
    {
        return [
            'name'    => $this->faker->name(),
            'shop_id' => $this->faker->randomNumber(),
        ];
    }
}

作成したfactoryの対象Modelへの紐付けも忘れずに行いましょう。HasFactoryトレイトを追加します。

use Illuminate\Database\Eloquent\Factories\HasFactory;

class Shop extends Model
{
    use HasFactory; //★
    ....
}
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Product extends Model
{
    use HasFactory; //★
    ....
}

ここまででで、factoryを使用する準備ができました。

factoryでリレーションデータを作成

では早速テスト用のダミーデータを作ってみましょう。前回の単一Modelデータの作成と同じ書き方で、簡単にリレーションデータが作成できます。

        $shop = Shop::factory()->create();

        Product::factory()->create([
            'shop_id' => $shop->shop_id
        ]);

まず親を作成し、子のデータを作成する際shop_idへ親のデータを渡すことでそれぞれのインスタンスを紐づけています。

これは見た目にも分かりやすくとっつきやすい書き方ですが、以下のようにforメソッドを使用するともっとコンパクトに記述することができます。

        $product = Product::factory()
            ->for(Shop::factory()->create())
            ->create();

している事は先ほどと同じで、Productと、それに関連するShopデータの作成です。

tinkerで実行してみると、以下のような結果となりました。

= App\Models\Product {#29792
    product_id: 36958,
    shop_id: 4763,
    name: "笹田 あすか",
  }

name属性に何も指定していないので、factoryで定義した$this->faker->name()で生成されたデータが入っています。
product_idはModelの主キーであるため、factoryで定義していなくてもカラムが生成されます。

また、shop_idには「4763」という値が入っています。これが親のshop_idと同じであればリレーションデータが正しく作成されている、ということになりますので、Shop情報をtinkerで確認してみましょう。

> $product->shop

= App\Models\Shop {#29755
    shop_id: 4763,
    name: "中島 康弘",
...
  }

ProductModelインスタンスが持っているshop_idと合致するShopModelインスタンスが取得できました!ちゃんとリレーションデータが作成されていますね。

属性をオーバーライドして任意の値に

では次は、テスト用に都合のよいデータを作成するため属性値をオーバーライドしてみます。createメソッドに上書きしたいデータを配列を渡してあげます。

        $product = Product::factory()
            ->for(Shop::factory()->create([
                'shop_id' => 1,
                'name'    => 'テスト店',
            ]))
            ->create([
                'product_id' => 100,
                'name'       => 'テスト商品',
            ]);

上記をtinkerで実行すると、以下のProductModelインスタンスが作成されました。ちゃんとproduct_id、name、そしてshop_idが反映されています。

= App\Models\Product {#29799
    product_id: 100,
    name: "テスト商品",
    shop_id: 1,
...
  }

リレーション先のShopModelインスタンスも確認してみます。

> $product->shop   

= App\Models\Shop {#29774
    shop_id: 1,
    name: "テスト店",
...
  }

shop_id、nameもセットした値になっていますね。

createManyで複数データ作成

また複数のProductを一気に作る場合、createManyを使うと簡潔に記述できます。

        $products = Product::factory()
            ->for(Shop::factory()->create(
                [
                    'shop_id' => 1,
                    'name'    => 'テスト店',
                ]
            ))
            ->createMany([
                [
                    'product_id'  => 50,
                    'name'        => 'テスト商品01'
                ],
                [
                    'product_id'  => 51,
                    'name'        => 'テスト商品02',
                ],
                [
                    'product_id'  => 52,
                    'name'        => 'テスト商品03',
                ]
            ]);

tinkerの実行結果は以下のようになりました。

= Illuminate\Database\Eloquent\Collection {#29727
    all: [
      App\Models\Product {#29730
        shop_id: 1,
        product_id: 50,
        name: "テスト商品01",
        ...
      },
      App\Models\Product {#29723
        shop_id: 1,
        product_id: 51,
        name: "テスト商品02",
        ...
      },
      App\Models\Product {#29814
        shop_id: 1,
        product_id: 52,
        name: "テスト商品03",
        ...
      },
    ],

ModelではなくCollectionが返ってきています。中身にはちゃんとProductModelインスタンスが3つ入っており、shop_idも指定した通りです。

簡単にダミーデータを作成でき、テスト作成にとても便利なので是非使ってみてくださいね。

By hmatsu