はじめに
Laravelのメジャーアップデート(11から12)および、パッケージ(spatie/laravel-data v3からv4)のアップデート作業中、新旧の挙動を比較するためにGitブランチを行き来する「タイムトラベル検証」を行いました。
その際、「Gitとコンテナの実体のズレ」「例外の握りつぶし」「インフラの生存スコープ」という3つの罠にハマり、解決に時間を要しました。
そのため備忘録として、実際に遭遇したエラーの全貌と解決策に関してまとめます。
内容について、お気づきの点がございましたら、お教えいただけますと幸いです。
前提
- Google Cloud上で動作する、PHPフレームワーク(Laravel)を使ったWebアプリケーション
- 認証サービス
- Firebase Authentication(Google)
- トークン形式
- JWT(JSON Web Token)
発端:アップデート前の挙動を過去に戻って確認
まずLaravel 11環境のベースとなるブランチから作業ブランチを派生させ、Laravel 12へのアップグレードに向けたコード修正を行いました。
一通り作業を終えて、「アップデート前のAPIレスポンス」と一致するかを確認するため、Gitのブランチを再び Laravel 11環境に切り替えました。
ブランチを変えたら composer install
Gitでブランチを切り替えても、vendor/ ディレクトリの中身(インストールされたパッケージの実体)は自動では過去に戻りません。
「コードは過去(v3)なのに、パッケージは未来(v4)」という不整合が起こることを防ぐため、ブランチ切り替え直後にコンテナ内で以下のコマンドを実行し、パッケージを同期させる必要があります。
// 当時の composer.lock の状態にパッケージをダウングレード composer install // 古いブランチの設定やクラス参照のキャッシュをリセットする php artisan optimize:clear
エラー:謎の「401 Unauthorized」
環境の同期も行い、PostmanからAPIリクエストを送信しました。
しかし、返ってきたのは 401 Unauthorized(認証エラー)でした。
- FirebaseのIDトークンの有効期限切れか? ➔ トークンを再発行してもNG。
- キャッシュがおかしい? ➔
php artisan cache:clearを叩いてもNG。
動いていたはずの過去のコードが、頑なに認証を拒否する状態に突入しました。
探求:握りつぶされていた「真のエラー」
JWTのデコード処理のコードを確認したところ、どんなエラーが起きても、一律で401エラーを返すようになっていました。
// 修正前のコード
try {
$decoded = JWT::decode($idToken, $keySet);
} catch (\Exception $e) {
// どんなエラーが起きても、一律で401エラー
throw new AuthenticationErrorException();
}
ここで「認証エラーではなく、別のシステムエラーが起きていて、それが握りつぶされて401に変換されているだけなのでは?」と仮説を立てました。
そして、一時的に catch の中に dd($e->getMessage()); を仕込んで出力させたところ、以下の事実が判明しました。
- 本当のエラー:
ErrorException: Undefined array key 0 - 発生箇所:
CachedKeySet(Googleの公開鍵をキャッシュから取り出す処理)の内部
根本原因:消えないRedisキャッシュの罠
エラーの原因は「キャッシュアダプターの変更に伴う、Redis内のデータの不整合」でした。
アップデート作業の中で、キャッシュを扱うライブラリを RedisCachePool から RedisAdapter に変更していました。
- Laravel 12のブランチで、新しい形式のキャッシュがRedisに保存される。
- Laravel 11のブランチに戻る。
- Laravel 11のライブラリが、Laravel 12の新しい形式のキャッシュをRedisから読み込もうとする。
- シリアライズ形式が違うため配列が壊れており、
[0]番目の鍵が存在せずクラッシュ。
「なぜ php artisan cache:clear で直らなかったのか?」
今回エラーを起こしていたデータは、Laravelの管轄外でRedis内の別空間にデータを保存していたため、クリアコマンドをすり抜けて以前のデータが残っていました。
解決策:Redisキャッシュを指定して物理的に消去する
原因が「Redis内に残留した形式違いのJWT公開鍵キャッシュ」だと分かれば、Redisの中身を指定してJWT公開鍵キャッシュを物理的に消去します。
docker exec -it [redisコンテナ名] redis-cli -n [任意のDB番号] KEYS "プレフィックス*" | xargs docker exec -i [redisコンテナ名] redis-cli -n [任意のDB番号] DEL
これにより、ライブラリが安全にGoogleのサーバーから正しい形式で公開鍵を再取得・再キャッシュするようになり、無事に401の壁を突破。Laravel 11時代の正常なレスポンスと対面することができました!
まとめ
今回の検証から得た3つの教訓についてまとめます。
- ブランチを変えたら「コンテナ内で」パッケージ同期
Gitはコードを過去に戻せますが、vendor/ の中身は戻してくれません。ブランチ切り替え直後は パッケージ更新(コンテナ内)と キャッシュクリアをセットで行う必要があります。
- 例外(Exception)の握りつぶしはデバッグの難易度を上げる
システムエラーを安易にビジネスエラー(401など)に変換すると、エラー原因の追求が難しくなります。できる限り元のエラーを記録する設計にするのが良いと思いました。
- インフラの「状態」はGitでは巻き戻らない
コードやパッケージを過去に戻しても、RedisやDBに書き込まれたデータは「最新」のままです。バージョンを行き来する際は、データストアのクリアもセットで考慮する必要があります。
同じように「バージョンを変えたら原因不明のエラーが出た」という方は、ぜひ「インフラの残留データ」を疑ってみてはいかがでしょうか。