Eloquent ORM非依存のfactory作った
LaravelのfactoryがEloquent ORM依存で、自前のDTOクラスに使えなくて困ったので自前ファクトリを作った
コード
app/Util/Fabrik.php
<?php declare(strict_types=1); namespace App\Util; use Closure; use Faker\Generator as Faker; use App\Util\FabrikBuilder; /** * ドイツ語で「工場」の意 * @package App\Util */ class Fabrik { /** @var Closure[] */ private $providers; /** @var Closure[] */ private $ctors; /** @var Faker */ private $faker; /** * @param Faker $faker */ public function __construct(Faker $faker) { $this->faker = $faker; } /** * @param string $className クラス名 * @param Closure $provider 配列つくるやつ * @return Fabrik */ public function define(string $className, Closure $provider): Fabrik { $this->providers[$className] = $provider; return $this; } /** * @param string $className クラス名 * @param Closure $ctor 配列からなんかつくるやつ * @return Fabrik */ public function constructBy(string $className, Closure $ctor): Fabrik { $this->ctors[$className] = $ctor; return $this; } /** * テスト用メソッド * @param string $className クラス名 * @param Closure $provider 配列つくるやつ * @return bool */ public function hasDefinition(string $className, Closure $provider): bool { return ($this->providers[$className] ?? null) === $provider; } /** * テスト用メソッド * @param string $className クラス名 * @param Closure $ctor 配列からなんかつくるやつ * @return bool */ public function hasCtor(string $className, Closure $ctor): bool { return ($this->ctors[$className] ?? null) === $ctor; } /** * @param string $className 構築対象のクラス */ public function of(string $className, int $count = 1): FabrikBuilder { return new FabrikBuilder( $this->faker, $this->providers[$className], $count, $this->ctors[$className] ?? null ); } }
app/Util/FabrikBuilder.php
<?php declare(strict_types=1); namespace App\Util; use Closure; use Faker\Generator as Faker; use Illuminate\Support\Collection; /** * @package App\Util */ class FabrikBuilder { /** @var Closure */ private $provider; /** @var Closure */ private $ctor; /** @var Faker */ private $faker; /** @var int */ private $count; public function __construct(Faker $faker, Closure $provider, int $count = 1, ?Closure $ctor = null) { $this->faker = $faker; $this->provider = $provider; $this->count = $count; $this->ctor = $ctor ?? function (array $arr) { return $arr; }; } /** * 構築する * - 構築数が1ならctorの戻り値の型 * - 2以上ならCollection * @return mixed|Collection */ public function make(array $override = []) { return $this->makeWith($this->ctor, $override); } /** * 構築する * - 構築数が1ならarray * - 2以上ならCollection * @return mixed|Collection */ public function makeArray(array $override = []) { return $this->makeWith( function (array $arr) { return $arr; }, $override ); } /** * 構築する * - 構築数が1ならctorの戻り値の型 * - 2以上ならCollection * @param Closure $ctor * @return mixed */ public function makeWith(Closure $ctor, array $override = []) { $provider = $this->provider; if ($this->count === 1) { return $ctor( array_merge( $provider($this->faker), $override ) ); } else { return collect()->times($this->count) ->map( function ($_) use ($ctor, $override, $provider) { return $ctor( array_merge( $provider($this->faker), $override ) ); } ); } } // ---------------------------------------- /** * テスト用メソッド * @param Faker $faker * @return bool */ public function hasFaker(Faker $faker): bool { return $this->faker === $faker; } /** * テスト用メソッド * @param Closure $provider 配列生成関数 * @return bool */ public function hasProvider(Closure $provider): bool { return $this->provider === $provider; } /** * テスト用メソッド * @param Closure $ctor 構築関数 * @return bool */ public function hasCtor(Closure $ctor): bool { return $this->ctor === $ctor; } /** * テスト用メソッド * @param int $count 構築数 * @return bool */ public function hasCount(int $count): bool { return $this->count === $count; } }
app/Facades/Fabrik.php
<?php declare(strict_types=1); namespace App\Facades; use Illuminate\Support\Facades\Facade; /** * オレオレFactoryのオレオレファサード * @package App\Facades */ class Fabrik extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return \App\Util\Fabrik::class; } }
config/app.php
<?php ... 'aliases' => [ ... // オレオレfactoryファサード 'Fabrik' => 'App\Facades\Fabrik', ],
app/Providers/FabrikServiceProvider
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use App\Util\Fabrik; use App\Domain\User\User; use App\Domain\User\UserProfile; use Faker\Generator as Faker; /** * オレオレfactory "Fabrik"に関するブートストラップロジックを書く */ class FabrikServiceProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { $this->app->singleton(Fabrik::class); } /** * Bootstrap services. * * @return void */ public function boot() { \Fabrik::define(User::class, function (Faker $faker) { $faker->unique(); return [ 'id' => null, 'email' => $faker->safeEmail, 'password' => $faker->password(), 'role' => 1, 'activationToken' => $faker->password(), 'rememberToken' => $faker->password() ]; })->constructBy(User::class, function (array $params) { return new User( $params['id'], $params['email'], $params['password'], $params['role'], $params['activationToken'], $params['rememberToken'] ); }); } }
利用側
factory()
のインタフェースに寄せたつもり- ブートストラップロジックの中で、所望のオブジェクトの構築方法を定義する
- 生の配列の生成
- 配列からオブジェクトの構築
<?php \Fabrik::define(User::class, function (Faker $faker) { $faker->unique(); return [ 'id' => null, 'email' => $faker->safeEmail, 'password' => $faker->password(), 'role' => 1, 'activationToken' => $faker->password(), 'rememberToken' => $faker->password() ]; })->constructBy(User::class, function (array $params) { return new User( $params['id'], $params['email'], $params['password'], $params['role'], $params['activationToken'], $params['rememberToken'] ); });
- こんな感じにオブジェクトを生成する
<?php // User $user = \Fabrik::of(User::class)->make(); // id=1なUser $user = \Fabrik::of(User::class)->make(['id' => 1]); // UserのCollection $users = \Fabrik::of(User::class, 3)->make(); // array $arr = \Fabrik::of(User::class)->makeArray();