【Larabel】クッキーの有効期限と削除タイミングを保存&テスト
背景
- 「最後に閲覧した◯◯の ID」をクッキーに保存しておきたい
- クッキーの有効期限を アプリ側のロジック(週末の日曜 23:59:59 など) に合わせたい
- かつ、開発中は
?date=で「今の時間」を好きに動かして、クッキーの削除タイミングを検証したい
この記事では、以下の 2 点を軸に実装・テストする方法をまとめます。
- 擬似現在日時 (
?date=) を使って「今」を差し替える - クッキーの中に有効期限を持たせて、
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 つです。
- クッキーの JSON に含めた
expires_atと、擬似現在日時getCurrentDateTime()を比較していること - 期限切れなら
Cookie::queue(Cookie::forget($cookieName))を呼んで、アプリ側のロジックとして削除を指示していること
こうしておくと、
- ブラウザ側の Expires
- アプリ側のロジック(擬似 now と expires_at)
の両面から、「いつ消えるべきだったか」を再現・検証できます。
4. 擬似日時を使ったテスト手順
4-1. クッキー発行の確認
-
開発環境で以下のような URL を叩く
例:http://localhost/xxx?date=2025-11-21 10:00:00&debug_cookie_minutes=5 -
該当画面で
queueLastResultItemCookie('sample001')が呼ばれるようにしておく -
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