この記事は個人ブログと同じ内容です
Laravel Date Filtering という、Eloquent でのデータ取得における日時フィルタリングを提供するパッケージを試してみます。
Laravel Date Filtering
Laravel Date Filtering は、Laravel Eloquent モデル向けの、日付に基づく高度なフィルタリングと操作を簡素化するパッケージです。さまざまな日付と時間の間隔に基づいてレコードをフィルタリングするための便利なメソッドセットを提供します。
https://github.com/omarelnaghy/lara-date-filter
インストールと初期設定
パッケージをインストールします。
composer require omar-elnaghy/laradate-filters
設定ファイルを publish します。
php artisan vendor:publish --provider="OmarElnaghy\LaraDateFilters\ServiceProvider"
config/lara_date_filter.php
が生成されます。
<?php return [ 'custom_date_filter_convention' => [ 'get{duration}{unit}Records', 'get{duration}{unit}Records', 'get{duration}{unit}Records', ], ];
日付をフィルタリングするメソッド FilterByDateRange, FilterByDateHoursRange, FilterByDateMinutesRange などを使用可能にするため、対象のモデルの newEloquentBuilder() メソッドをオーバーライドしますします。
今回は User モデルで実装していきます。
use Illuminate\Database\Eloquent\Model; use OmarElnaghy\LaraDateFilters\Traits\Builder\PackageBuilder; class User extends Model { . . (略) . /** * @param QueryBuilder $query * * @return PackageBuilder */ public function newEloquentBuilder($query): PackageBuilder { return new PackageBuilder($query); } }
オーバーライドはしたものの、結果として Builder を extends し本パッケージ用のメソッドを Traits で付与したクラスを返しているため、ベースである Builder クラスに変更はありません。
これでこのパッケージを使い始める準備が整いました。
User データ
これからフィルタリングを行っていきますが、 今回使用しているデータは、 created_at が 2023-09-01 〜 2023-09-30 までの 30 日の範囲で 1 名ずつ User を作成した全 30 件のデータで行っています。
created_at は datetime で収録していますが、時間は全て 00:00:00 です。
フィルタリング:filterByDateRange
ここからは、実際のフィルタリングを見ていきたいと思います。
Laravel Date Filtering の基本メソッドである filterByDateRange() メソッドを使ってみます。
$startDate = Carbon::parse('2023-09-03');
$duration = 2;
$dateUnit = 'day';
$range = DateRange::INCLUSIVE;
$direction = 'after';
// User をフィルタリング
$users = User::filterByDateRange($duration, $dateUnit, $startDate, $direction, $range)->get();
各項目を解説する前に、上記条件のフィルタリングによって得られた結果を先に共有しておきます。
// $users(配列に変換し必要なフィールドのみに限定しています。実際には\Illuminate\Database\Eloquent\Collection<int, \App\Models\User> が返ります。) Array ( [0] => Array ( [id] => 3 [created_at] => 2023-09-03 00:00:00 ) [1] => Array ( [id] => 4 [created_at] => 2023-09-04 00:00:00 ) [2] => Array ( [id] => 5 [created_at] => 2023-09-05 00:00:00 ) )
filterByDateRange メソッドの各引数は以下の意味とバリエーションになります。
/** * @param int $duration * @param string $dateUnit * @param Carbon $date * @param string $direction * @param DateRange $range */ public function filterByDateRange(int $duration, string $dateUnit, Carbon $date, string $direction = 'after', DateRange $range = DateRange::INCLUSIVE)
- $duration
- 範囲の数値
- $dateUnit
- $duration の単位
- 'second'
- 'minute'
- 'hour'
- 'day'
- 'week'
- 'month'
- 'year'
- $duration の単位
- $date
- 基点日
- $direction
- 基点日から過去・未来どちらの方向へ範囲を取るか
- 'before'(過去)
- 'after'(未来)
- 基点日から過去・未来どちらの方向へ範囲を取るか
- $range
- 範囲指定における、起点、及び終点を含めるか
- INCLUSIVE(含まれる)
- EXCLUSIVE(含まれない)
- 範囲指定における、起点、及び終点を含めるか
$duration = 1, $dateUnit = 'day'
なら、 1 日間ということになります。
この時、$duration = 0
なら、起点日のみという扱いになります。
また、$range が少しややこしいので説明しておくと、
$startDate = 9/3, $duration = 2, $dateUnit = 'day', $direction = 'after'
とした場合、DateRange::INCLUSIVE なら
9/3 <= targets <= 9/5
となり、9/3, 9/4, 9/5 のデータが返されます。
DateRange::EXCLUSIVE の場合は
9/3 < targets < 9/5
となり、9/4 のデータが返されます。
クエリビルダとの違い
ここで、Laravel Date Filtering を用いた最初の実装を再度見てみます。
$startDate = Carbon::parse('2023-09-03');
$duration = 2;
$dateUnit = 'day';
$range = DateRange::INCLUSIVE;
$direction = 'after';
// User をフィルタリング
$users = User::filterByDateRange($duration, $dateUnit, $startDate, $direction, $range)->get();
このときに、同じ実装をクエリビルダで行うとどうなるか見てみます。
日付範囲を内包する場合
Laravel Date Filtering でいうところの $range が DateRange::INCLUSIVE の場合です。
$startDate = CarbonImmutable::parse('2023-09-03'); $duration = 2; $endDate = $startDate->addDays($duration); $users = User::whereBetween('created_at', [$startDate, $endDate])->get();
クエリビルダでも割合シンプルに書けそうです。では日付範囲を内包しない場合はどうでしょうか。
日付範囲を内包しない場合
Laravel Date Filtering でいうところの $range が DateRange::EXCLUSIVE の場合です。
$startDate = CarbonImmutable::parse('2023-09-03'); $duration = 2; $endDate = $startDate->addDays($duration); $users = User::where(function (Builder $query) use ($startDate, $endDate) { $query->whereDate('created_at', '>', $startDate) ->whereDate('created_at', '<', $endDate); })->get();
若干記述するコード量は増えましたが、まあ書けなくはなさそうです。
Laravel Date Filtering とクエリビルダでの日付フィルタリング
2 つを比べてみると、記述量にさほど違いはないものの、クエリビルダの場合は日付範囲の条件によって Builder の組み立て自体が変化します。
対して Laravel Date Filtering パッケージの場合は、そこを引数で吸収しているため、日付範囲の条件によって式を変えなくても良いという利点がありそうです。
その他のフィルタリングメソッド
filterByDateRange メソッドの他にも、以下のメソッドが提供されています。
- filterByDateSecondsRange
- filterByDateMinutesRange
- filterByDateHoursRange
- filterByDateDaysRange
- filterByDateWeeksRange
- filterByDateMonthsRange
これらは filterByDateRange メソッドにおける $dateUnit の省略版になっています。
Custom Eloquent Builder
Laravel Date Filtering ではカスタム Eloquent ビルダーを提供しており、メソッドの名前を "filterByDateXRange" パターンに従って命名すると、動的にメソッドを生成し、実行します。
// [Duration] は検索したい [日付単位(Date Unit)] の数を示す数値
return Post::filterByDate(Duration)(Date Unit)Range($startDate, $direction, $range)->get();
例えば、以下のようなメソッドが任意で組み立て可能です。
Post::filterByDate3DayRange($startDate, $direction, $range)->get(); Post::filterByDate3WeekRange($startDate, $direction, $range)->get(); Post::filterByDate6MonthRange($startDate, $direction, $range)->get();
created_at 以外のカラムでフィルタリングするには
(あくまでも 2023 年 9 月 22 日時点です)
Laravel Date Filtering のフィルタリングは、基本的に created_at を見るようになっています。
created_at 以外のカラムでフィルタリングする、 ないし動的にカラムを指定する方法は提供されていませんでした。
モデルに public $dateColumn = 'updated_at';
と指定すればフィルタリングのカラム変更はできるものの、あまり実用的ではないため、このあたりの開発はまだこれからのようです。
今後に期待の楽しみなパッケージ
本記事執筆が 2023 年 9 月 22 日ですが、Laravel Date Filtering は first commit が 2023 年 9 月 13 日と、まだ公開されて間もないパッケージです。
日付での複雑なフィルタリングが頻発するアプリケーションでは重宝しそうなパッケージなので、今後の機能拡充に期待しています。
https://github.com/omarelnaghy/lara-date-filter
現在 back check 開発チームでは一緒に働く仲間を募集中です。 herp.careers