Laravel で業務システムを作っていると、「年度」「四半期」「月次」などの期間計算があちこちに散らばりがちです。
- 集計処理の中に年度判定が書かれている
- コントローラで「4月1日〜翌年3月31日」を毎回計算している
- 月次レポートのロジックがサービス層とバッチで微妙に違う
こうした “日付ロジックの分散” は、後から仕様変更が入ったときに必ず足を引っ張ります。
- 「年度の開始月が変わった」
- 「四半期の区切りを変えたい」
- 「月末の扱いを統一したい」
これらを 全ファイルを grep して修正するのは、誰にとっても幸せではありません。
期間ロジックは「値オブジェクト」に閉じ込める
そこでおすすめなのが、期間を表すクラスを作り、すべての計算をそこに集約するという設計です。
Laravel では Carbon が強力なので、値オブジェクトとの相性も抜群です。
例えば「年度」を扱うなら、次のようなクラスを用意します。
<?php
namespace App\Support\TargetPeriod;
use Carbon\Carbon;
/**
* 年度(4月1日開始・翌年3月31日終了)を表します。
*/
class FiscalYear implements TargetPeriod
{
private int $year;
private array $quarters = [];
public function __construct(Carbon $date)
{
$this->year = $date->month >= 4 ? $date->year : $date->year - 1;
}
public function __toString(): string
{
return $this->value();
}
public function startDate(): Carbon
{
return Carbon::create($this->year, 4, 1, 0, 0, 0);
}
public function endDate(): Carbon
{
return Carbon::create($this->year + 1, 3, 31, 23, 59, 59);
}
public function value(): string
{
return (string) $this->year;
}
public function label(): string
{
return $this->year . '年度';
}
public function quarters(): array
{
if (empty($this->quarters)) {
for ($i = 0; $i < 4; $i++) {
$this->quarters[] = new TargetQuarter($this->startDate()->addMonths($i * 3));
}
}
return $this->quarters;
}
}
こうしておくと何が嬉しいのか
1. 年度ロジックが一箇所に集約される
年度の開始月を 4 月 → 1 月に変えたい場合でも、修正はこのクラスだけ。
2. サービス層・バッチ・ビューで同じ計算結果を共有できる
「年度の文字列表現」「開始日」「終了日」「四半期」など、すべて統一される。
3. テストが書きやすい
値オブジェクトは入力と出力が明確なので、ユニットテストが非常に簡単。
4. コードの意図が読みやすくなる
値オブジェクトとして期間を表現しておくと、Eloquent のスコープにそのまま渡せるというメリットがあります。
例えば、注文モデルに「指定された期間の注文だけを絞り込む」スコープを定義するとこうなります。
use Illuminate\Database\Eloquent\Builder;
use App\Support\TargetPeriod\TargetPeriod;
class Order extends Model
{
#[Scope]
protected function ofTargetPeriod(Builder $query, TargetPeriod $targetPeriod): void
{
$query->whereBetween('ordered_at', [
$targetPeriod->startDate(),
$targetPeriod->endDate(),
]);
}
}
これで、コントローラやサービス層では次のように直感的に書けます。
$fy = new FiscalYear(now());
$orders = Order::ofTargetPeriod($fy)->get();
「年度の注文を取得している」という意図がコードから読み取れるので、ビジネスロジックの意味がそのままコードに現れるのが大きな利点です。
5. 集計期間も簡単に階層化できる
年度 → 四半期 → 月次 のように階層的な集計を行う場合、
期間ロジックを値オブジェクトに閉じ込めておくと、Blade 側の集計処理も自然な形で書けます。
例えば、次のようなレポートを作るとします。
- 月次集計
- 四半期集計
- 年度集計
期間クラスがしっかりしていれば、Blade は次のように書けます。
@foreach ($fy->quarters() as $quarter)
@foreach ($quarter->months() as $month)
{{-- 月次集計 --}}
@endforeach
{{-- 四半期集計 --}}
@endforeach
{{-- 年度集計 --}}
このコードは、
「年度 → 四半期 → 月次」というビジネス構造をそのまま表現している
という点が非常に重要です。
まとめ
日付計算や期間の表現は、業務システムの中でも特に “散らばりやすい” ロジックです。
しかし、値オブジェクトとして一箇所に集約するだけで、拡張性・可読性・保守性が劇的に向上します。
さらに、TargetPeriod インターフェイスを用意しておけば、年度以外にも以下のような期間クラスを追加できます。
- TargetMonth(月次)
- TargetQuarter(四半期)
- Last30Days(直近30日)
- CustomPeriod(任意の開始日・終了日)
どれを渡しても同じように動くように、期間ロジックを抽象化できるのもポイントです。

