※本記事にはアフィリエイト広告が含まれます。
Laravel 12で動いているアプリを保守していて、「そろそろ13に上げないといけないが、何から手を付ければいいのか」と考えている人に向けた記事です。
公式のアップグレードガイドは、変更点をコンポーネントごとに並べています。それぞれに影響度(Likelihood Of Impact)が書かれていますが、並び順は影響度の順ではありません。この記事では、公式ガイドの影響度に沿って変更点を並べ直し、「自分のアプリが当たるかどうかをどこで調べるか」を添えて整理します。私も実務でPHP / Laravelを使っていますが、この記事ではアップグレードを実際に実行した結果は扱いません。内容はすべて公式ドキュメントの記載にもとづいており、日付やバージョンは2026年9月26日に確認した時点のものです。
結論:作業はPHP 8.3以上→依存パッケージ→CSRF→キャッシュとセッションの順
先に作業の順番をまとめます。
- 本番環境・CI・開発環境のPHPを8.3以上にする
composer.jsonの依存パッケージを13系に合わせて更新する(High)VerifyCsrfTokenを参照している箇所をPreventRequestForgeryに書き換える(High)- キャッシュにPHPオブジェクトを入れているか、MySQL/MariaDBで
upsertを使っているかを確認する(Medium) - セッション・キャッシュのプレフィックスなど、影響度はLowでも利用者に見える変更を確認する
公式ガイドでは、アップグレードの見積もり時間は「10 Minutes」とされています。ただし同じガイドに、変更点の一部はフレームワークのあまり使われない部分にあり、実際にアプリに影響するのはその一部だけだろう、とも書かれています。10分というのは標準的な構成を想定した目安で、独自のミドルウェアの設定やキャッシュの使い方しだいで作業量は変わる、と考えておくのが無難です。
サポート期限も確認しておきます。公式リリースノートのサポート表では、次のようになっています。
| バージョン | 対応PHP | リリース | バグ修正の期限 | セキュリティ修正の期限 |
|---|---|---|---|---|
| Laravel 12 | 8.2〜8.5 | 2025-02-24 | 2026-08-13 | 2027-02-24 |
| Laravel 13 | 8.3〜8.5 | 2026-03-17 | 2027年第3四半期 | 2028-03-17 |
確認日の時点で、Laravel 12はすでにバグ修正の期間を過ぎており、セキュリティ修正だけを受けている状態です。セキュリティ修正の期限(2027-02-24)までに移行を終えるなら、年内に計画を立てておくと余裕があります。
前提:Laravel 13はPHP 8.3以上が必須
リリースノートには「Laravel 13.x requires a minimum PHP version of 8.3.」と書かれています。Laravel 12はPHP 8.2でも動きましたが、13ではPHP 8.2は対象外になりました。
PHP 8.2そのものも、php.netのサポート表ではセキュリティサポートが2026年12月31日で終わります。PHP 8.3のセキュリティサポートは2027年12月31日まで、PHP 8.4は2028年12月31日まで、PHP 8.5は2029年12月31日までです。
いまPHP 8.2で動かしているなら、Laravelのアップグレードとは別の作業として、先にPHPのバージョンを上げておくのがおすすめです。PHPとLaravelを同時に上げると、不具合が出たときにどちらが原因かの切り分けが難しくなるためです。Laravel 12はPHP 8.3〜8.5にも対応しているので、「Laravel 12のままPHPだけ上げる」という段階を踏めます。
PHP 8.2から8.3への変更点は、php.netの移行ガイド「Migrating from PHP 8.2.x to PHP 8.3.x」にまとまっています。「Backward Incompatible Changes」(後方互換性のない変更)と「Deprecated Features」(非推奨になった機能)の節を読み、自分のコードや依存パッケージに当たるものがないかを確認しておきましょう。
影響が大きい変更(High)
依存パッケージの更新
公式ガイドでは、composer.json の次の依存パッケージを更新するよう求めています。
laravel/frameworkを^13.0にlaravel/boostを^2.0にlaravel/tinkerを^3.0にphpunit/phpunitを^12.0にpestphp/pestを^4.0に
見落としやすいのはテストツールです。PHPUnitやPestのメジャーバージョンが上がると、テストコード側の書き換えが必要になることがあります。フレームワーク本体の変更とは別に、テストツールの移行にも時間を見ておきましょう。また、上の一覧はLaravel公式のパッケージが中心です。サードパーティのパッケージがLaravel 13に対応しているかは、各パッケージのリリース情報で個別に確認する必要があります。
CSRFミドルウェアの名前の変更(VerifyCsrfToken→PreventRequestForgery)
CSRF対策のミドルウェアの名前が VerifyCsrfToken から PreventRequestForgery に変わりました。あわせて、Sec-Fetch-Site ヘッダを使ってリクエストの送信元を検証する処理が加わっています。
旧名の VerifyCsrfToken と ValidateCsrfToken は非推奨のエイリアスとして残ります。ただし公式ガイドでは、テストやルート定義でミドルウェアを除外している箇所を中心に、直接の参照を PreventRequestForgery に書き換えるよう求めています。公式の例は次のとおりです。
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
// Laravel <= 12.x
->withoutMiddleware([VerifyCsrfToken::class]);
// Laravel >= 13.x
->withoutMiddleware([PreventRequestForgery::class]);
ミドルウェアの設定用のAPIにも preventRequestForgery(...) が加わっています。bootstrap/app.php でCSRFの除外設定をしている場合は、ここもあわせて確認しておきましょう。
影響が中程度の変更(Medium)
キャッシュの serializable_classes
キャッシュの設定に serializable_classes というオプションが加わり、既定値は false です。公式ガイドによると、これは APP_KEY が漏れた場合に、PHPのデシリアライズを悪用した攻撃(gadget chain攻撃)を防ぎやすくするための変更です。キャッシュに意図してPHPオブジェクトを保存しているアプリでは、デシリアライズを許可するクラスを明示する必要があります。
'serializable_classes' => [
App\Data\CachedDashboardStats::class,
App\Support\CachedPricingSnapshot::class,
],
自分のアプリが当たるかどうかは、Cache::remember や Cache::put に渡している値を見て判断します。Eloquentのモデルやコレクション、独自のDTOなどのオブジェクトを渡している箇所が対象です。公式ガイドでは、そうした箇所を許可リストで明示するか、配列などオブジェクトでない形式に移すよう求めています。
MySQL / MariaDBのupsertとuniqueBy
upsert の uniqueBy に空の値を渡すと、InvalidArgumentException が投げられるようになりました。これまでは不正なSQLが組み立てられていたところを、事前に弾くようになった形です。
注意したいのは、MySQLとMariaDBのドライバは uniqueBy の値をもともと使っていない(テーブルの主キーとユニークインデックスで既存の行を判定する)のに、この検証は適用される点です。「MySQLでは意味がないから空で渡していた」というコードが、13では例外になります。upsert( で検索して、第2引数に空の配列などを渡していないか確認しましょう。
Lowでも利用者に影響が出る変更
影響度がLowの変更の中にも、本番の利用者から見える形で問題になりうるものがあります。
セッションのserializationをjsonに変えると全セッションが無効になる
Laravel 13のアプリの雛形では、config/session.php の serialization が json になっています。公式ガイドによると、既存のアプリで設定ファイルを13の雛形に合わせて php から json に変えると、ログイン中のすべてのユーザーのセッションが無効になります。
アップグレードの前後でセッションを維持したいなら、この値は php のままにしておきます。セッションにPHPオブジェクトを入れておらず、ユーザーに再ログインしてもらってよい場合は、セキュリティのために json への変更が推奨されています。設定ファイルを雛形とまとめて同期するときに、うっかり変わらないよう注意が必要です。
キャッシュ・Redisのプレフィックスとセッションクッキー名の既定値
キャッシュとRedisのキーのプレフィックス、セッションクッキー名の既定値が、アンダースコア区切りからハイフン区切りに変わりました。
// Laravel <= 12.x
Str::slug((string) env('APP_NAME', 'laravel'), '_').'_cache_';
Str::slug((string) env('APP_NAME', 'laravel'), '_').'_database_';
Str::slug((string) env('APP_NAME', 'laravel'), '_').'_session';
// Laravel >= 13.x
Str::slug((string) env('APP_NAME', 'laravel')).'-cache-';
Str::slug((string) env('APP_NAME', 'laravel')).'-database-';
Str::slug((string) env('APP_NAME', 'laravel')).'-session';
公式ガイドによると、多くのアプリではアプリ側の設定ファイルでこれらの値を定義済みなので、この変更は当たりません。影響を受けるのは、アプリ側の設定に値がなく、フレームワーク側の既定値に頼っている場合です。その場合はアップグレード後にキャッシュのキーやセッションクッキー名が変わり、既存のキャッシュが読めなくなったり、ログイン状態が切れたりする可能性があります。以前の動きを保つには、環境変数で CACHE_PREFIX、REDIS_PREFIX、SESSION_COOKIE を明示します。
MySQLのDELETE…JOINにORDER BY / LIMITが付くようになった
MySQLのクエリビルダで JOIN を含む DELETE を組み立てるとき、ORDER BY と LIMIT もSQLに含まれるようになりました。以前はこれらが黙って無視されることがありました。この構文をサポートしないデータベース(MySQL、および11.8.1より前のMariaDB)では、範囲を絞らない削除が実行される代わりに QueryException が投げられる可能性があります。join と delete を組み合わせ、orderBy や limit を付けているクエリがないか確認しておきましょう。
ドメイン付きルートの優先順位、キューのイベントのプロパティ名
- ドメインを明示したルートが、ドメインのないルートより先にマッチするようになりました。サブドメインで振り分けているアプリは、ルートの解決結果が変わらないか確認が必要です
JobAttemptedイベントの真偽値のプロパティ$exceptionOccurredが、例外オブジェクト(またはnull)を持つ$exceptionに置き換わりましたQueueBusyイベントのプロパティ$connectionが$connectionNameに名前を変えました
キューのイベントを監視やアラートのリスナーで使っている場合、プロパティ名の変更に気づかないと、通知が届かなくなるといった形で問題になります。
見落としやすい項目:array_first()の衝突など
Laravel 13は symfony/polyfill-php85 に依存するようになりました。PHP 8.5未満では、このポリフィルが array_first() や array_last() といったグローバル関数を定義します(起動時にすでに同名の関数が定義されている場合を除く)。
問題になるのは、laravel/helpers のような古いヘルパーパッケージや、独自に定義した同名のグローバル関数と衝突する場合です。公式ガイドによると、昔の array_first() ヘルパーはコールバックを受け取って条件に合う最初の要素を返しましたが、ポリフィル版は配列の最初の要素を返すだけです。どちらの関数が先に定義されるかで動きが変わりうるため、公式ガイドでは Illuminate\Support\Arr のメソッドに置き換えるよう勧めています。
use Illuminate\Support\Arr;
Arr::first($array, function ($value) {
return /* condition */;
});
古くから保守しているアプリほど、ヘルパー関数を使っている可能性が高いので注意が必要です。
もう1つ、Bootstrap 3用のページネーションのビュー名が変わりました。pagination::default は pagination::bootstrap-3 に、pagination::simple-default は pagination::simple-bootstrap-3 になります。旧名を直接指定している箇所は書き換えが必要です。
アップグレード前のチェックリスト
ここまでの内容を、プロジェクト全体を検索するときのキーワードとしてまとめます。ヒットした箇所が、この記事で紹介した変更に当たるかを1つずつ確認してください。
| 検索キーワード | 確認すること | 影響度 |
|---|---|---|
VerifyCsrfToken / ValidateCsrfToken | PreventRequestForgery への書き換え | High |
Cache:: / cache( | オブジェクトを保存していれば serializable_classes に登録 | Medium |
upsert( | uniqueBy が空でないか | Medium |
'serialization'(config/session.php) | 意図せず json に変わっていないか | Low |
CACHE_PREFIX / REDIS_PREFIX / SESSION_COOKIE | 既定値に頼っていないか | Low |
exceptionOccurred / QueueBusy | イベントのプロパティ名の変更 | Low |
array_first / array_last / laravel/helpers | Arr::first() / Arr::last() への置き換え | Low |
pagination::default / pagination::simple-default | 新しいビュー名への書き換え | Low |
ここで取り上げた以外にも、公式ガイドには影響度がLowやVery Lowの変更が載っています(Container::call の挙動、キャッシュやキューのContractへのメソッドの追加、パスワードリセットのメールの既定の件名など)。独自のキャッシュストアやキュードライバを実装している場合は、該当する節を必ず読んでください。
作業を自動化する手段についても、公式ガイドで2つ紹介されています。1つはLaravel公式のMCPサーバーであるLaravel Boostです。Laravel 12のアプリにBoostを入れ、Claude CodeやCursorなどのAIアシスタントで /upgrade-laravel-v13 というスラッシュコマンドを実行すると、13へのアップグレードを案内に沿って進められます(Boost ^2.0 が必要)。もう1つはLaravel Shiftで、コミュニティが運営する、Laravelのアップグレードを自動化するサービスです。どちらを使う場合も、この記事のチェックリストで変更の結果を確認しておくと安心です。
まとめ
Laravel 12から13へのアップグレードは、公式リリースノートでも破壊的な変更を最小限にしたリリースとされています。それでも、次の順に進めると抜け漏れを防ぎやすくなります。
- PHPを8.3以上にする(Laravel 12のまま先に上げられる)
composer.jsonの依存パッケージを更新し、テストツールの移行も見込む- CSRFミドルウェアの参照を
PreventRequestForgeryに書き換える - キャッシュの
serializable_classesとupsertのuniqueByを確認する - セッションの
serialization、プレフィックスの既定値など、利用者に見える変更を確認する
メジャーバージョンを上げるのは、フレームワークの仕組みを学び直すよい機会でもあります。ミドルウェアやキャッシュ、キューの仕組みを体系的に押さえ直したい人は、Laravelの技術書を1冊手元に置いておくと、公式ドキュメントと行き来しながら理解を深められます。
書籍を選ぶときは、対象のLaravelのバージョンを確認し、公式ドキュメントで最新の仕様と照らし合わせながら読むのがおすすめです。
フレームワークのアップグレードのような保守の経験は、PHPエンジニアとして案件を選ぶときの強みにもなります。案件の選び方については「PHPエンジニアが失敗しないSES案件の選び方」、クラウドのスキルと単価の関係については「PHPエンジニアがAWSも取ったらフリーランス単価はどう変わるか」にまとめています。


