Laravel:factoryとtrait

0
153

Laravel 8.xからfactoryの形が変わり、期待する処理内容をクラスとして定義できるようになったようですが、現在のLTSである6.xでは以下のようにクロージャとして登録する形になっています。

$factory->define(TargetModel::class, function (Faker $faker) {
    return [
        'column1' => $faker->word,
        'column2' => $faker->numberBetween(1,100),
        ...
    ];
});

一方で、様々な局面で使い回したい機能をtraitにしておくと言うことも良く用いる手法なのですが、factoryでこのtraitを使いたくなった際に困りました。
最初に書いたように8.x以降であればfactoryがクラス化されているのでtraitを使用することは造作もないのですが、上記のようにクロージャとしてしか定義できない6.xでは単純にはtraitが使えません。

と言うことでネットで色々と調べてみたところ、やはり同様の悩みを持った方がいたようで、以下のような方法が提示されていました。

$factory->define(TragetModel::class, (new class {
    use CommonTrait; // trait使用宣言

    public function generatorFunction() {
        return function (Faker $faker) {
            return [
                'column1' => $faker->word,
                'column2' => self::traitMethod(), // traitのメソッド使用
                ...
            ];
        };
    }
})->generatorFunction());

無名クラスを作成し、同クラス内にfactory実行時に呼び出させたいクロージャを返すメソッド(上記例ではgeneratorFunction)を定義します。
無名クラス内ではtraitが使えるため、上記クロージャ内の処理で当該traitのメソッドを使用できます。
上記クロージャを返すメソッドの実行結果をfactoryの第二引数(本来であれば直接クロージャを定義する箇所)に渡すことで、factory実行時に当該クロージャが呼び出されるようになると言うことのようです。
なかなかトリッキーな方法であるためパッと見では分かりにくいですが、頑張って読み解くと筋は通っているように思えますし、実際に使ってみると確かに期待したように動きます。

今回は特にfactoryに関連した話として紹介していますが、引数にクロージャーを指定する処理で、当該クロージャ内においてtraitの機能を使いたい場合などには同じ方法が使えそうです。
ただ、可読性的には難ありな印象ですので、余程困った時に使用する技としておくくらいが良いかと。