🐕

【Larabel】クッキーの有効期限と削除タイミングを保存&テスト

に公開

背景

  • 「最後に閲覧した◯◯の ID」をクッキーに保存しておきたい
  • クッキーの有効期限を アプリ側のロジック(週末の日曜 23:59:59 など) に合わせたい
  • かつ、開発中は ?date= で「今の時間」を好きに動かして、クッキーの削除タイミングを検証したい

この記事では、以下の 2 点を軸に実装・テストする方法をまとめます。

  1. 擬似現在日時 (?date=) を使って「今」を差し替える
  2. クッキーの中に有効期限を持たせて、Cookie::queue(Cookie::forget()) で明示的に削除する

1. 擬似現在日時を返すヘルパーを用意する

まず「今何時か?」を一元管理するメソッドを作ります。
開発環境だけ ?date= クエリパラメータを使って擬似日時を扱えるようにします。

use Carbon\Carbon;

class DateTimeComponent
{
    public static function getCurrentDateTime(): Carbon
    {
        // 開発環境だけ ?date=YYYY-MM-DD HH:MM:SS を見にいく
        if (app()->environment('local')) {
            $dateParam = request()->query('date'); // 例: ?date=2025-11-21 10:00:00
            if (is_string($dateParam) && trim($dateParam) !== '') {
                try {
                    return Carbon::parse($dateParam, 'Asia/Tokyo');
                } catch (\Throwable $e) {
                    // パースできない場合は無視して通常の now を使う
                }
            }
        }

        // 本番やパラメータ無しのときは通常の現在日時
        return Carbon::now('Asia/Tokyo');
    }
}

以降、**「現在時刻が欲しいところでは Carbon::now() ではなく、このメソッドを呼ぶ」**のがポイントです。


2. クッキーに「値 + 有効期限」を JSON で保存する

次に、「最後に閲覧した ID」をクッキーに保存する処理を実装します。

ここでは例としてクッキー名を last_result_itemcd とし、

  • 通常は「今週の日曜 23:59:59」まで有効
  • 開発環境では ?debug_cookie_minutes=◯ で有効期限を短縮

という仕様にしています。

use Carbon\Carbon;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Cookie as SymfonyCookie;

class SomeComponent
{
    public static function queueLastResultItemCookie(string $itemcd): void
    {
        $cookieName = 'last_result_itemcd';

        // 疑似現在日時(?date=… を考慮)
        $now = DateTimeComponent::getCurrentDateTime();

        // デフォルトの有効期限:今週の日曜 23:59:59
        $endOfWeek = $now->copy()
            ->endOfWeek(Carbon::SUNDAY)
            ->setTime(23, 59, 59);

        // ロジック上の期限(後で上書きする可能性あり)
        $expiresAt = $endOfWeek->copy();

        // デバッグ用途:?debug_cookie_minutes=10 などで期限を短くできる
        if (app()->environment('local')) {
            $debugMinutes = request()->query('debug_cookie_minutes');

            if (is_numeric($debugMinutes)) {
            $debugMinutes = max(3, (int) $debugMinutes); // 最低3分くらいは確保
                $expiresAt = $now->copy()->addMinutes($debugMinutes);
            }
        }

        // クッキーに保存する値を JSON にまとめる
        $cookieValue = json_encode([
            'itemcd'     => $itemcd,
            'expires_at' => $expiresAt->toIso8601String(), // サーバ側ロジック
                                                                    用の期限
        ]);

        // ブラウザ側の Expires も expiresAt に揃える
        $symfonyCookie = new SymfonyCookie(
            $cookieName,
            $cookieValue,
            $expiresAt, // ← 絶対時刻で有効期限を指定
            '/',
            null,
            false,      // 本番では true(Secure)を推奨
            false,
            false,
            'Lax'
        );

        Cookie::queue($symfonyCookie);

        // ログ(開発時のデバッグ用)
        if (app()->environment('local')) {
            $realNow = Carbon::now('Asia/Tokyo');
            Log::debug('[CookieDebug] last_result_itemcd set/update', [
                'debug_now'  => $now->toDateTimeString(),     // 擬似現在日時
                'expires_at' => $expiresAt->toDateTimeString(), // ロジック上の
                                                                          期限
                'real_now'   => $realNow->toDateTimeString(),   // 実サーバ時間
                'itemcd'     => $itemcd,
            ]);
        }
    }
}

ポイントは:

  • クッキーの中身をただの文字列ではなく JSON にしていること
  • JSON の中に expires_at を入れておくことで、後述する「読み出し&削除判定」で使い回せること
  • ブラウザの Expires と、アプリ側の expires_at を同じ値で揃えていること

3. クッキーを読むときに Cookie::forget() で明示的に削除する

保存したクッキーを読むとき、
ただ Cookie::get() で値を取るだけでなく、「期限切れなら削除」も同時に行います。

public static function getLastResultItemFromCookie(): ?string
{
    $cookieName = 'last_result_itemcd';

    if (!Cookie::has($cookieName)) {
        return null;
    }

    $raw = Cookie::get($cookieName);
    if (empty($raw)) {
        return null;
    }

    $data = json_decode($raw, true);
    if (!is_array($data)) {
        return null;
    }

    $itemcd       = $data['itemcd']     ?? null;
    $expiresAtStr = $data['expires_at'] ?? null;

    if ($itemcd === null || $expiresAtStr === null) {
        return null;
    }

    $now        = DateTimeComponent::getCurrentDateTime();      // 擬似現在日時
    $expiresAt  = Carbon::parse($expiresAtStr, 'Asia/Tokyo');

    // 期限切れ判定:now > expires_at の場合は削除
    if ($now->greaterThan($expiresAt)) {
        Cookie::queue(Cookie::forget($cookieName));

        if (app()->environment('local')) {
            Log::debug('[CookieDebug] last_result_itemcd expired -> forget queued', [
                'now'        => $now->toDateTimeString(),
                'expires_at' => $expiresAt->toDateTimeString(),
                'itemcd'     => $itemcd,
            ]);
        }

        return null;
    }

    // 期限内なら itemcd を返す
    return $itemcd;
}

ここでのポイントは 2 つです。

  1. クッキーの JSON に含めた expires_at と、擬似現在日時 getCurrentDateTime() を比較していること
  2. 期限切れなら Cookie::queue(Cookie::forget($cookieName)) を呼んで、アプリ側のロジックとして削除を指示していること

こうしておくと、

  • ブラウザ側の Expires
  • アプリ側のロジック(擬似 now と expires_at)

の両面から、「いつ消えるべきだったか」を再現・検証できます。


4. 擬似日時を使ったテスト手順

4-1. クッキー発行の確認

  1. 開発環境で以下のような URL を叩く
    例:

    http://localhost/xxx?date=2025-11-21 10:00:00&debug_cookie_minutes=5
    
  2. 該当画面で queueLastResultItemCookie('sample001') が呼ばれるようにしておく

  3. Chrome DevTools → Application → Cookies

    • last_result_itemcd が作成されていること
    • Value に {"itemcd":"sample001","expires_at":"..."}
    • Expires が expires_at と近い値になっていること
      を確認

4-2. 期限内アクセスの確認

?date=2025-11-21 10:03:00

のように、expires_at より前の擬似日時でアクセスします。

  • getLastResultItemFromCookie()sample001 を返す
  • ログにも「expired → forget」のメッセージは出ない
  • クッキーは残ったまま

4-3. 期限切れアクセスの確認

?date=2025-11-21 10:07:00

のように、「expires_at を明らかに過ぎた擬似日時」でアクセスします。

  • now > expires_at なので、Cookie::queue(Cookie::forget(...)) が実行される
  • ログに expired -> forget queued が出る
  • 次のリロード以降、クッキーがブラウザから消える

OS の時計を変えたり、わざわざ数日待ったりしなくても、
URL パラメータだけで「有効期限内 / 有効期限外」を再現できるのがこの構成のメリットです。


まとめ

  • 現在日時は Carbon::now() ではなく「擬似現在日時ヘルパー」に集約する
    ?date= で時間を自由に動かせる

  • クッキーには

    • 表示に使う値(今回は itemcd
    • 有効期限の情報(expires_at
      JSON で一緒に保存する
  • 読み出し時には expires_at と擬似現在日時を比較し、
    有効期限を過ぎていたら Cookie::queue(Cookie::forget()) で明示的に削除する

  • 開発環境では ?debug_cookie_minutes を用意して、有効期限までの残り時間自体も短くできるようにすると、
    保存 → 期限内アクセス → 期限切れアクセス → 削除確認 までを一気にテストしやすくなる

このパターンは「バナー表示制御」「お知らせの既読情報」「おすすめの一時保存」など、
期限付きでクッキーに残したい情報全般にそのまま応用できるので、
1つ仕組みを作っておくとかなり便利です

Discussion