laravelでseederを作成する際に、factoryでModelごとの定義をしていた時に、一意な日付を設定する必要がありました。
例えば、営業日を設定する際に重複が発生しないようにするなどがあるかと思います。
laravelのバージョンは10です。
unique関数について
Seeder側でstateを利用して外部から設定させることも可能ですが、Seederが複雑になりそうです。
Fakerではunique関数を利用することで、関数ごとに返した値がキャッシュされ、一意な値が返ってくるようになります。
参考)https://fakerphp.github.io/#modifiers
うまくいかない一意の日付
このunique関数を利用して、FakerPHPの日付を返す関数を利用できれば、一意な日付を返すことができそうです。
ということで、下記のようなFactoryを作成しました。
public function definition(): array
{
return [
// ...
'bussiness_date' => fake()->unique()->dateTimeThisYear(),
];
}が、重複した日付が登録されてしまいます。
実装の確認
関数名からして原因はあきらかですが、dateTimeThisYearの実装を確認します。
public function dateTimeThisYear($until = 'last day of december', string $timezone = null): \DateTime
{
return $this->dateTimeBetween('first day of january', $until, $timezone);
}
https://github.com/FakerPHP/Faker/blob/v1.23.1/src/Faker/Core/DateTime.php#L133-L136
続いて、dateTimeBetweenの実装確認します。
public function dateTimeBetween($from = '-30 years', $until = 'now', string $timezone = null): \DateTime
{
$start = $this->getTimestamp($from);
$end = $this->getTimestamp($until);
if ($start > $end) {
throw new \InvalidArgumentException('"$from" must be anterior to "$until".');
}
$timestamp = $this->generator->numberBetween($start, $end);
return $this->setTimezone(
$this->getTimestampDateTime($timestamp),
$timezone,
);
}https://github.com/FakerPHP/Faker/blob/v1.23.1/src/Faker/Core/DateTime.php#L93-L108
ということで、秒単位で日時を生成しているため、日付で見たときに一意にならないことがわかりました。
日付を返す関数を追加する
FakerPHPには日付を返す関数がないため、時間が00:00:00である\DateTimeを返す関数を追加出ればよいことがわかりました。
FakerProviderファイルの追加
app/Faker/Provider/Date.phpを追加します。
<?php
namespace App\Faker\Provider;
use Faker\Provider\Base;
class Date extends Base
{
public function dateThisYear($until = 'last day of december', string $timezone = null): \DateTime
{
return $this->generator->dateTimeBetween('first day of january', $until, $timezone)->setTime(0, 0, 0);
}
}
先述のdateTimeThisYearとほとんど同じですが、最後に->setTime(0, 0, 0)をして、時間を00:00:00にしています。
ServiceProviderの追加
app/Providers/FakerServiceProvider.phpを追加します。
<?php
namespace App\Providers;
use App\Faker\Provider\Date;
use Illuminate\Support\ServiceProvider;
class FakerServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
$faker = fake();
$faker->addProvider(new Date($faker));
}
/**
* Bootstrap services.
*/
public function boot(): void
{
//
}
}
providerの登録
config/app.phpのproviders配列の最後に追記し、FakerServiceProviderを登録します。
'providers' => [
/*
* Application Service Providers...
*/
// ...
App\Providers\FakerServiceProvider::class,
],
動作確認
下記のようにDate.phpで定義したdateThisYear関数を読みだすことで、一意な日付が設定されることが確認できました。
public function definition(): array
{
return [
// ...
'bussiness_date' => fake()->unique()->dateThisYear(),
];
}[AD]
