PHPに深刻な脆弱性が見つかり、「PHPを新しいバージョンに上げてください」と言われた。ところが開発会社に相談すると、「Laravel 5(または6)で作られているので、PHPだけを上げることはできません」と返ってきた。何年も前に作ったLaravelのシステムを使い続けている会社で、今まさに起きている相談です。
「PHPの脆弱性対応でバージョンを上げたいのに、Laravelが古くて上げられないと言われた。何年も見送ってきたシステムを、段階的に上げるのか作り直すのか、どう決めればいいのか…」
先に結論をお伝えします。Laravel 5系はPHP 8に対応しておらず、Laravel 6も対応はPHP 8.0までです。そのPHP 8.0も、Laravel 5・6自体も、すでにセキュリティ修正が終わっています。 PHPを安全なバージョン(8.2以降)に上げるには、Laravelも一緒に上げる必要があります。そして一般的なMVC構成(画面・処理・データを分けた標準的な作り)のLaravelアプリなら、作り直さなくても 「Laravelを1世代ずつ、PHPを階段状に」上げていく方法 で、Laravel 12+PHP 8.4までたどり着けることが多いです。
この記事では、Laravel 6(PHP 7.4)で作られた受注管理システムを例に、Laravel 12(PHP 8.4)まで上げる具体的な手順を、コード例つきで解説します。Laravel 5系(5.5〜5.8)で作られている場合は、先に「Laravel 5系から始める場合」の手順でLaravel 6まで上げてから、同じ流れで進めます。技術的な内容が多いため、発注者の方は「足かせになる理由」「段階的に上げるか作り直すか」「工数が膨らむ条件」と最後のチェックリスト・質問例だけ読んでも大丈夫です。社内の技術担当者や、見積もりの中身を確かめたい方は手順まで読んでみてください。(バージョン・サポート期限は執筆時点の2026年10月の情報です)
Laravel 5・6がPHPバージョンアップの足かせになる理由
フレームワークごとに「動かせるPHPの範囲」が決まっている
Laravel(=PHPでWebシステムを作るための土台となる部品集)は、バージョンごとに対応するPHPの範囲が決まっています。Laravelの公式ドキュメントに載っているサポート表をまとめると、次のとおりです。
📰 出典:Laravel 6.x Release Notes(Support Policy)
📰 出典:Laravel 9.x Release Notes(Support Policy)
📰 出典:Laravel 13.x Release Notes(Support Policy)
| Laravel | 対応PHP | リリース | セキュリティ修正の期限 |
|---|---|---|---|
| 5.5(LTS) | 7.0以上(PHP 8は非対応) | 2017年8月 | 2020年8月30日(終了) |
| 5.6〜5.8 | 7.1.3以上(PHP 8は非対応) | 2018年2月〜2019年2月 | 最も新しい5.8でも2020年2月26日(終了) |
| 6(LTS) | 7.2〜8.0 | 2019年9月 | 2022年9月6日(終了) |
| 7 | 7.2〜8.0 | 2020年3月 | 2021年3月3日(終了) |
| 8 | 7.3〜8.1 | 2020年9月 | 2023年1月24日(終了) |
| 9 | 8.0〜8.2 | 2022年2月 | 2024年2月6日(終了) |
| 10 | 8.1〜8.3 | 2023年2月 | 2025年2月4日(終了) |
| 11 | 8.2〜8.4 | 2024年3月 | 2026年3月12日(終了) |
| 12 | 8.2〜8.5 | 2025年2月 | 2027年2月24日 |
| 13 | 8.3〜8.5 | 2026年3月 | 2028年3月17日 |
一方、PHP本体のサポート状況は次のとおりです(2026年10月時点)。
| PHP | セキュリティ修正の期限 | 状態 |
|---|---|---|
| 7.4 / 8.0 / 8.1 | 終了済み | 修正が出ない |
| 8.2 | 2026年12月31日 | 間もなく終了 |
| 8.3 | 2027年12月31日 | セキュリティ修正のみ |
| 8.4 | 2028年12月31日 | 通常サポート中 |
| 8.5 | 2029年12月31日 | 通常サポート中 |
2つの表を並べると、問題がはっきりします。Laravel 5系のままだとPHPは7系が上限、Laravel 6でも8.0が上限で、どちらももう修正が出ません。 脆弱性が見つかっても、PHP 8.0向けの修正版は公開されないのです。PHPの脆弱性への対応については、次の記事でも解説しています。


「PHPだけ先に上げる」ができない仕組み
Laravelのプロジェクトには composer.json という「使う部品とそのバージョンの一覧表」があり、Laravel 6が依存する部品には「PHP 8.0まで」、Laravel 5系の部品には「PHP 7系まで」という条件が書かれています。そのためサーバーのPHPを8.1以上にすると、部品の入れ直し(composer install)が失敗したり、PHPの仕様変更で古い部品が正しく動かなかったりします。
つまり、PHPを上げるには 「新しいPHPに対応したLaravelに上げる」→「PHPを上げる」 の順番を守る必要があります。これが「フレームワークのPHP対応上限が足かせになる」状態です。
段階的に上げるか、作り直すかの判断の目安
何年も更新していないシステムでは、「いっそ作り直したほうが早いのでは」という話も出ます。目安は次のとおりです。
| 観点 | 段階的なバージョンアップが向く | 作り直しを検討する |
|---|---|---|
| 作り | Laravel標準のMVC構成に沿っている | フレームワークの外側に独自の仕組みが多い |
| 部品 | 使っている外部パッケージが少ない・今も更新されている | 開発が止まった部品に深く依存している |
| 業務 | 業務の流れは今もほぼ同じ | 業務が大きく変わり、画面や機能も見直したい |
| 情報 | ソースコードと本番環境の情報が手元にある | 誰も中身を把握しておらず、資料もない |
| 期限 | 脆弱性対応など、早く安全な状態にしたい | 時間をかけてでも全体を作り替えたい |
Laravelは公式に「メジャーバージョンアップは1日以内で済むよう努めている」と説明しており、標準的な作りなら1世代ごとの変更点は限られます。業務の見直しを伴わないなら、まずは段階的に上げるのが基本の選択肢です。作り直しは、業務の見直しと同時に行う場合や、段階的に上げることが現実的でないほど独自の作りが多い場合に検討します。
いつ・どこまで対応すべきかの判断の考え方は、次の記事で詳しく解説しています。


全体の進め方:Laravelは1世代ずつ、PHPは階段状に上げる
いきなり composer.json を「Laravel 12」に書き換えても、変更点が何世代分もまとめて押し寄せ、どこで壊れたのか分からなくなります。Laravelは1世代ずつ(5系なら5.5→5.6→5.7→5.8→6、その後は6→7→8→9→10→11→12)上げ、PHPは 「今のLaravelでも次のLaravelでも動くバージョン」に切り替える のが鉄則です。
| 段階 | Laravel | PHP | この段階の主な作業 |
|---|---|---|---|
| 0 | 今のバージョンの最新パッチ | 今のまま | 検証環境・テストを整える |
| A | 5.5 → 5.6 | 7.1(7.1.3以上) | ログ・パスワード暗号化の設定ファイル追加 |
| B | 5.6 → 5.7 | 7.1 → 7.2 | Bladeの or 記法の置き換え |
| C | 5.7 → 5.8 | 7.2 | キャッシュの有効期限が「分」から「秒」に |
| D | 5.8 → 6 | 7.2 → 7.4 | 文字列・配列ヘルパーの置き換え、権限チェック |
| 1 | 6 → 7 | 7.4 | 例外処理の書き方、日付の出力形式 |
| 2 | 7 → 8 | 7.4 → 8.0 | ファクトリ・シーダーの作り替え、ページ送りの見た目 |
| 3 | 8 → 9 | 8.0 → 8.1 | ファイル保存とメール送信の部品の入れ替え |
| 4 | 9 → 10 | 8.1 → 8.2 | 日付項目の書き方、廃止された機能の置き換え |
| 5 | 10 → 11 | 8.2 → 8.3 | マイグレーション(=DB変更の手順書)の書き方 |
| 6 | 11 → 12 | 8.3 → 8.4 | 日付ライブラリCarbon 3への対応 |
1段階ごとに「部品を上げる → テストが全部通る → 画面で確認 → 記録(コミット)」を繰り返します。本番への反映は、必ずしも段階の数だけ分ける必要はありません。たとえば「5.5→5.8」「5.8→7」「7→9」「9→11」「11→12」のように 2〜3段階ずつまとめてリリースする と、確認の手間と1回あたりのリスクのバランスが取りやすくなります。
この記事で想定するサンプル:一般的なMVC構成の受注管理システム
手順を具体的にするため、Laravel 6で作られた次のような受注管理システムを想定します。社内の担当者が顧客と受注を登録し、受注時に確認メールを送り、請求書PDFを保存する、よくある業務システムです。
app/
├── User.php / Customer.php / Order.php … モデル(Laravel 6 の標準どおり app/ 直下)
├── Http/Controllers/OrderController.php … コントローラ(一覧・登録・PDFダウンロード)
├── Http/Requests/StoreOrderRequest.php … 入力チェック(フォームリクエスト)
├── Mail/OrderConfirmed.php … 受注確認メール
├── Jobs/GenerateInvoicePdf.php … 請求書PDFを作るジョブ(キュー)
├── Exceptions/Handler.php
└── Providers/RouteServiceProvider.php
database/
├── factories/OrderFactory.php … テスト用データの定義(旧形式)
├── migrations/
└── seeds/DatabaseSeeder.php
resources/views/orders/index.blade.php … 画面(Blade)
routes/web.php … Route::resource('orders', 'OrderController');
tests/Feature/OrderTest.php
composer.json の主な部分は次のとおりです(Laravel 6の標準構成に近いもの)。
"require": {
"php": "^7.2",
"fideloper/proxy": "^4.0",
"laravel/framework": "^6.20",
"laravel/tinker": "^2.0",
"laravel/ui": "^1.0"
},
"require-dev": {
"facade/ignition": "^1.4",
"fzaninotto/faker": "^1.4",
"mockery/mockery": "^1.0",
"nunomaduro/collision": "^3.0",
"phpunit/phpunit": "^8.0"
}
ステップ0:バージョンアップの前に土台を整える
最初の準備が、全体の工数と安全性を左右します。ここを省くと、後の段階でトラブルの原因を切り分けられなくなります。
0-1. 現状を棚卸しする
まず、今の環境を正確に把握します。
php -v # 本番と同じPHPバージョンか
composer show laravel/framework # Laravelの正確なバージョン
composer outdated --direct # 直接使っている部品と最新版の差
composer why-not php 8.1 # PHP 8.1 への切り替えを止めている部品の一覧
composer why-not は「このバージョンにできない理由」を部品ごとに表示してくれるコマンドで、足かせになっている部品を洗い出すのに便利です。あわせて、laravel/framework 以外に入っている外部パッケージ(PDF生成、Excel出力、管理画面など)を一覧にし、それぞれが今も更新されているか、Laravel 12に対応した版があるかを確認しておきます。開発が止まった部品が見つかったら、代わりの部品に置き換える作業が別途必要になります。
0-2. PHPのバージョンを切り替えられる検証環境を作る
本番サーバーで試すわけにはいかないので、Dockerで「PHPのバージョンだけを差し替えられる」検証環境を用意します。
# docker/php/Dockerfile
ARG PHP_VERSION=7.4
FROM php:${PHP_VERSION}-fpm
RUN apt-get update && apt-get install -y git unzip libzip-dev \
&& docker-php-ext-install pdo_mysql zip
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
# compose.yaml(抜粋)
services:
app:
build:
context: ./docker/php
args:
PHP_VERSION: ${PHP_VERSION:-7.4}
volumes:
- ./:/var/www/html
PHP_VERSION=8.0 docker compose build app のように指定するだけで、同じソースコードを別のPHPで動かせます。DBのバージョン(MySQL 5.7など)も本番に合わせておきましょう。
0-3. 主要な業務の流れをテストで押さえる
段階的なバージョンアップの安全装置は 自動テスト です。テストがほとんど無いシステムなら、まず「これが壊れたら業務が止まる」流れだけでもテストを書きます。受注管理なら、一覧表示・受注登録・入力エラー・メール送信・PDF保存あたりです。
// tests/Feature/OrderTest.php(Laravel 6 の書き方)
public function test_受注を登録すると確認メールが送られる()
{
Mail::fake();
$user = factory(User::class)->create();
$customer = factory(Customer::class)->create();
$response = $this->actingAs($user)->post('/orders', [
'customer_id' => $customer->id,
'amount' => 50000,
'ordered_at' => '2026-10-01',
]);
$response->assertRedirect('/orders');
$this->assertDatabaseHas('orders', ['customer_id' => $customer->id, 'amount' => 50000]);
Mail::assertSent(OrderConfirmed::class);
}
public function test_金額が未入力ならエラーになる()
{
$user = factory(User::class)->create();
$this->actingAs($user)->post('/orders', ['customer_id' => 1])
->assertSessionHasErrors('amount');
}
テストでカバーしきれない画面は、「確認する画面と操作の一覧(確認表)」を作り、各段階の後に同じ手順で目視確認します。
0-4. 今のバージョンの最新パッチに揃え、作業用のブランチを切る
git switch -c upgrade/laravel12
composer update laravel/framework --with-all-dependencies
今使っているバージョンの中での最新版(Laravel 6なら6.20系、5.5なら5.5系の最新)に揃え、テストが通ることを確認してから出発します。本番DBのバックアップと、個人情報を伏せた検証用データも用意しておきましょう。
Laravel 5系から始める場合:5.5 → 6の手順
Laravel 5系(5.5〜5.8)で作られている場合は、まずLaravel 6まで上げます。Laravel 5系の時代は「5.5」「5.6」のように2つ目の数字がメジャーバージョンにあたり、5.5→5.6→5.7→5.8→6と、ここも1つずつ上げます。Laravel 6から始める場合は、次の「ステップ1」に進んでください。
サンプルの受注管理システムがLaravel 5.5で作られている場合、composer.json はおおむね次のようになっています。
"require": {
"php": ">=7.0.0",
"fideloper/proxy": "~3.3",
"laravel/framework": "5.5.*",
"laravel/tinker": "~1.0"
},
"require-dev": {
"filp/whoops": "~2.0",
"fzaninotto/faker": "~1.4",
"mockery/mockery": "~1.0",
"phpunit/phpunit": "~6.0"
}
Laravel 5系はPHP 7.0〜7.1で動かしていることも多いため、PHPも段階的に7.2→7.4へ上げていきます。Laravel 5系の時代には、テストの書き方やログの設定ファイルも今と違うため、ステップ0のテスト整備はここで特に効いてきます。
ステップA:Laravel 5.5 → 5.6(PHP 7.1.3以上)
📰 出典:Laravel 5.6 Upgrade Guide
"php": ">=7.1.3",
"fideloper/proxy": "^4.0",
"laravel/framework": "5.6.*",
"phpunit/phpunit": "^7.0"
Laravel 5.6はPHP 7.1.3以上が必須です。PHP 7.0で動かしている場合は、先にPHPを7.1に上げます(Laravel 5.5はPHP 7.1でも動きます)。
- 設定ファイルの追加:ログの設定が
config/logging.php、パスワード暗号化の設定がconfig/hashing.phpという新しいファイルに分かれました。Laravel 5.6の標準のファイルをコピーして置き、config/app.phpのlog・log_levelは削除します。 - TrustProxies:
fideloper/proxy4系への更新に伴い、$headersプロパティの書き方が配列からRequest::HEADER_X_FORWARDED_ALLのような値に変わりました。 - 画面の文字の二重エスケープ:Blade(=画面のテンプレート)で
{{ }}を使ったとき、すでに&のように変換済みの文字も、もう一度変換されるようになりました。画面に&のような表示が出たら、この影響です。 - ページ送りの見た目:標準のデザインがBootstrap 4になりました。Bootstrap 3の見た目を保つなら
Paginator::useBootstrapThree();を追加します。 composer.jsonのscriptsにphp artisan optimizeがあれば削除します(コマンド自体が廃止されました)。
ステップB:Laravel 5.6 → 5.7、PHPを7.2へ
📰 出典:Laravel 5.7 Upgrade Guide
composer.json の laravel/framework を 5.7.* にします。画面側で影響が大きいのは、Bladeの or 記法が廃止された点です。古いLaravelのコードによくある書き方なので、全画面を検索して置き換えます。
{{-- Laravel 5.6 まで --}}
{{ $customer->tel or '未登録' }}
{{-- Laravel 5.7 以降:PHP の ?? 演算子に置き換える --}}
{{ $customer->tel ?? '未登録' }}
storage/framework/cache/dataフォルダの作成が必要です(アップグレードガイドの手順どおりに作成)。Route::redirectは、標準で一時的な転送(302)になりました。恒久的な転送(301)にしたい場合はRoute::permanentRedirectを使います。SEOに関わるので、旧URLの転送設定があれば確認しましょう。- テストで
$this->artisan()を使ってコマンドを実行している場合、出力の扱いが変わります。
Laravel 5.7でテストが通ったら、PHPを7.2に上げます。PHP 7.2で最も影響が大きいのは、配列やCountable(=数えられるもの)以外を count() に渡すと警告が出るようになった点です。Laravelは警告を例外に変換するため、画面がエラーで止まります。
$ids = $request->input('order_ids'); // チェックボックスが1つも選ばれていないと null
$total = count($ids); // PHP 7.2 以降は警告 → Laravel ではエラー画面になる
$total = count((array) $ids); // 配列に変換してから数える
あわせて、PHP 7.2では each() 関数や create_function() が非推奨になりました(PHP 8.0で削除)。
📰 出典:PHP 7.1.x から PHP 7.2.x への移行(PHPマニュアル)
ステップC:Laravel 5.7 → 5.8(PHP 7.2)
📰 出典:Laravel 5.8 Upgrade Guide
laravel/framework を 5.8.* にします。Laravel 5.8は、公式のアップグレードガイドで影響度が「Very High」とされている変更を含む、見落とすと気づきにくい変更が多い段階です。
キャッシュの有効期限が「分」から「秒」に変わりました。 数字で有効期限を指定している箇所は、そのままだと60分の1の時間でキャッシュが消えます。エラーにはならないため、テストでも見つかりにくい変更です。
// Laravel 5.7 まで:30分間キャッシュ / Laravel 5.8 以降:30秒でキャッシュが消える
Cache::put('order_summary', $summary, 30);
// どちらのバージョンでも30分になる書き方
Cache::put('order_summary', $summary, now()->addMinutes(30));
.env ファイルの読み込み方も変わりました。 引用符で囲んでいない値の中の # 以降は、コメントとして無視されます。パスワードやAPIキーに # が含まれていると、ログインや外部連携が突然失敗します。
# Laravel 5.7 まで: abc#123 / Laravel 5.8 以降: abc(# 以降が消える)
MAIL_PASSWORD=abc#123
# 引用符で囲めば、どちらでも abc#123
MAIL_PASSWORD="abc#123"
str_limit()・array_get()などの文字列・配列ヘルパー関数が非推奨になりました(Laravel 6で削除。次の段階で対応)。- 日付ライブラリCarbonの2系に対応しました。可能ならこの段階でCarbon 2に上げておきます(Laravel 6でCarbon 1は使えなくなります)。
- パスワードリセット時のパスワードの最低文字数が8文字になりました。
- テストクラスの
setUp()・tearDown()には: voidを付けます。 - モデル名が「UserFeedback」のように不規則な複数形で終わる場合、テーブル名の推測が変わります(
$tableを明示すれば従来どおり)。
ステップD:Laravel 5.8 → 6、PHPを7.4へ
📰 出典:Laravel 6.x Upgrade Guide
"php": "^7.2",
"laravel/framework": "^6.20",
"facade/ignition": "^1.4",
"nunomaduro/collision": "^3.0",
"phpunit/phpunit": "^8.0"
Laravel 6はPHP 7.2以上が必須です。エラー画面の部品は、新しいLaravel 6の標準構成にあわせて filp/whoops から facade/ignition に入れ替えると、この後のステップ1の手順とそろいます。
文字列・配列ヘルパー関数が本体から削除されました。 古いLaravelのコードでは画面やコントローラーのあちこちで使われているため、影響範囲が広い変更です。当面は公式の互換パッケージ laravel/helpers を入れて動かし、順次 Str・Arr クラスに書き換えるのが現実的です。
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
// Laravel 5.8 まで
$summary = str_limit($order->note, 50);
$tel = array_get($data, 'customer.tel');
// Laravel 6 以降
$summary = Str::limit($order->note, 50);
$tel = Arr::get($data, 'customer.tel');
- 権限チェック:コントローラーで
authorizeResource()を使っている場合、ポリシー(=誰が何をしてよいかを決めるクラス)に一覧画面用のviewAnyメソッドが必要になりました。無いと一覧画面が「権限なし」で表示されなくなります。 Inputファサードの削除:Input::get('name')はRequest::input('name')(または$request->input('name'))に書き換えます。- キューの再試行回数:
php artisan queue:workは、標準で1回失敗したら再試行しなくなりました。サーバーのプロセス管理(Supervisorなど)の設定で--triesを指定しているか確認し、失敗したジョブを記録するfailed_jobsテーブルも用意します。 - Redisの接続方式:標準がPHPの拡張機能(phpredis)に変わりました。従来の
predisを使い続けるなら、config/database.phpのredis.clientにpredisを明示します。 - 文字列を主キーにしているモデルには
protected $keyType = 'string';を指定します。
Laravel 6でテストが通ったら、PHPを7.4に上げます。PHP 7.4では、null や数値に対して配列のように [0] でアクセスすると注意メッセージ(Notice)が出るようになりました。Laravelは注意メッセージも例外に変換するため、エラー画面になります。ここまで来れば、記事冒頭のサンプルと同じ「Laravel 6+PHP 7.4」の状態です。ここからはステップ1に進みます。
📰 出典:PHP 7.3.x から PHP 7.4.x への移行(PHPマニュアル)
Laravel 5.4以前で作られている場合
Laravel 5.4以前の場合も、考え方は同じです。5.4→5.5と1つずつ上げてから、この手順に合流します。ただし、PHP 5系で動いていることが多く、PHP本体の変更点(PHP 5→7)も重なるため、段階の数と確認の手間はさらに増えます。業務の見直しを予定しているなら、作り直しとの比較も、開発会社にあわせて相談してみてください。
ステップ1:Laravel 6 → 7(PHPは7.4のまま)
📰 出典:Laravel 7.x Upgrade Guide
部品のバージョンを上げる
composer.json を次のように書き換え、composer update を実行します。
"laravel/framework": "^7.0",
"laravel/tinker": "^2.0",
"laravel/ui": "^2.0",
"facade/ignition": "^2.0",
"nunomaduro/collision": "^4.1",
"phpunit/phpunit": "^8.5"
ログイン画面などに laravel/ui を使っている場合、Laravel 7からは本番環境でも必要になるため、require-dev ではなく require に置きます。
例外処理の型を Throwable に変える
Laravel 7は内部の部品(Symfony)が5系に上がったため、例外ハンドラーの引数の型を Exception から Throwable に変えます。
// app/Exceptions/Handler.php
use Throwable;
public function report(Throwable $exception)
{
parent::report($exception);
}
public function render($request, Throwable $exception)
{
return parent::render($request, $exception);
}
キューのジョブに failed(Exception $e) を書いている場合も failed(Throwable $e) に変えます。Artisanコマンド(=コマンドラインで動かす処理)の handle() が値を返している場合は、整数(成功なら 0)を返すようにします。
日付のJSON出力形式が変わる点に注意
見落としやすいのが、モデルをJSONに変換したときの日付の形式です。Laravel 7から 2019-12-02 20:01:00 形式ではなく、2019-12-02T20:01:00.283041Z(ISO-8601形式・UTC)で出力されます。画面のJavaScriptや外部システムがこの値を読んでいる場合、表示がずれたり連携が失敗したりします。従来の形式を保つなら、モデルに次のメソッドを追加します。
use DateTimeInterface;
protected function serializeDate(DateTimeInterface $date)
{
return $date->format('Y-m-d H:i:s');
}
このほか、同じ名前のルートを2つ定義していないか、Blade::component を使っていないか(Blade::aliasComponent に改名)、テストの assertSee に自分でエスケープした値を渡していないかも確認します。
ステップ2:Laravel 7 → 8、PHPを8.0へ
📰 出典:Laravel 8.x Upgrade Guide
部品のバージョンを上げる
"php": "^7.3|^8.0",
"guzzlehttp/guzzle": "^7.0.1",
"laravel/framework": "^8.0",
"laravel/ui": "^3.0",
"facade/ignition": "^2.5",
"fakerphp/faker": "^1.9.1",
"mockery/mockery": "^1.4.2",
"nunomaduro/collision": "^5.0",
"phpunit/phpunit": "^9.3"
テスト用のダミーデータを作る fzaninotto/faker は開発が終了しているため、後継の fakerphp/faker に置き換えます。PHP 8で動かすためにも必要な入れ替えです。
シーダー・ファクトリの置き場所と名前空間を変える
Laravel 8では、テストデータを作る仕組み(シーダー・ファクトリ)に名前空間(=クラスの住所のようなもの)が必要になりました。database/seeds フォルダを database/seeders に名前変更し、各シーダーの先頭に namespace Database\Seeders; を追加します。composer.json の autoload も次のように変えます。
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
}
(従来ここにあった classmap の指定は削除します)
ファクトリをクラス形式に書き換える
ファクトリの書き方が全面的に変わりました。まずは公式の互換パッケージ laravel/legacy-factories を入れて旧形式のまま先に進み、後でクラス形式に書き換える、という2段構えにすると作業を分けられます。
// Laravel 7 まで:database/factories/OrderFactory.php
$factory->define(Order::class, function (Faker $faker) {
return [
'customer_id' => factory(Customer::class),
'amount' => $faker->numberBetween(1000, 100000),
'ordered_at' => $faker->dateTimeThisYear,
];
});
// Laravel 8 以降:database/factories/OrderFactory.php
namespace Database\Factories;
use App\Customer;
use App\Order;
use Illuminate\Database\Eloquent\Factories\Factory;
class OrderFactory extends Factory
{
protected $model = Order::class;
public function definition()
{
return [
'customer_id' => Customer::factory(),
'amount' => $this->faker->numberBetween(1000, 100000),
'ordered_at' => $this->faker->dateTimeThisYear(),
];
}
}
モデル側には use HasFactory; を追加し、テストの factory(Order::class, 3)->create() は Order::factory()->count(3)->create() に書き換えます。
そのほか、Laravel 8で確認すること
- ページ送りの見た目:標準のデザインがTailwind CSSに変わりました。Bootstrapの見た目を保つなら、
AppServiceProviderのboot()にPaginator::useBootstrap();を追加します。 - キューの設定名:ジョブの
retryAfterはbackoffに、timeoutAtはretryUntilに改名されました。 - メンテナンスモード:
php artisan down --messageは廃止されました。public/index.phpにメンテナンスモード用の数行を追加します(アップグレードガイドに記載)。 - ルートの書き方:
RouteServiceProviderの$namespaceを残していれば、'OrderController@index'形式のルートはそのまま動きます。新しい書き方([OrderController::class, 'index'])への変更は後回しで構いません。 - モデルの置き場所:Laravel 8から新規プロジェクトのモデルは
app/Modelsに置かれますが、既存のapp/Order.phpを移動する必要はありません。移動するなら、バージョンアップとは別の作業として行うほうが安全です。
PHPを8.0に切り替える
Laravel 8でテストが通ったら、検証環境のPHPを8.0に切り替えます(PHP_VERSION=8.0)。PHP 8.0では言語の仕様がいくつか変わっており、アプリ側のコードで影響が出やすいのは次の点です。
📰 出典:PHP 7.4.x から PHP 8.0.x への移行(PHPマニュアル)
// 文字列と数値の比較ルールが変わった
$code = $request->input('code'); // 例: 'abc'
if ($code == 0) { // PHP 7: true / PHP 8: false
// 「未入力なら0扱い」のつもりで書いた分岐が通らなくなる
}
create_function()・each()・money_format()など、古い関数が削除された- 必須の引数の前に省略可能な引数を書く書き方が非推奨になった
- 内部関数に想定外の型を渡すと、警告ではなくエラー(TypeError)で止まるようになった
matchが予約語になった(同名の関数やクラスがあると動かない)
この段階で、PHPのバージョンに合わせてコードを自動で書き換えるツール Rector を使うと、機械的な修正の手間を減らせます。
// rector.php(例:PHP 8.0 の書き方に揃える)
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([__DIR__.'/app', __DIR__.'/database', __DIR__.'/routes', __DIR__.'/tests'])
->withPhpSets(php80: true);
vendor/bin/rector process --dry-run で変更内容を確認してから適用します。自動修正の結果もテストと目視で必ず確認しましょう。
📰 出典:Rector(GitHub)
ステップ3:Laravel 8 → 9、PHPを8.1へ
📰 出典:Laravel 9.x Upgrade Guide
Laravel 9はPHP 8.0以上が必須で、ファイル保存とメール送信の土台の部品が入れ替わる、6→12の中で最も変更点が多い段階 です。
部品のバージョンを上げる
"php": "^8.0.2",
"laravel/framework": "^9.0",
"nunomaduro/collision": "^6.1",
"spatie/laravel-ignition": "^1.0",
"phpunit/phpunit": "^9.5.10"
facade/ignition(エラー画面の部品)は spatie/laravel-ignition に置き換えます。また、fideloper/proxy は不要になるので削除し、app/Http/Middleware/TrustProxies.php を次のように書き換えます。ロードバランサーの後ろでシステムを動かしている場合、ここを間違えるとHTTPSの判定やアクセス元IPの記録がおかしくなるので注意が必要です。
use Illuminate\Http\Middleware\TrustProxies as Middleware; // 旧: Fideloper\Proxy\TrustProxies
use Illuminate\Http\Request;
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
ファイル保存(Flysystem 3)の挙動の変化
ファイル保存の部品(Flysystem)が1系から3系に上がり、「ファイルが無いときの動き」が変わります。サンプルの請求書PDFダウンロードのように、例外を前提にした書き方は直す必要があります。
// Laravel 8 まで:ファイルが無いと例外が出る前提
try {
$pdf = Storage::disk('local')->get("invoices/{$order->id}.pdf");
} catch (FileNotFoundException $e) {
abort(404);
}
// Laravel 9 以降:存在確認してから読む
$path = "invoices/{$order->id}.pdf";
if (! Storage::disk('local')->exists($path)) {
abort(404);
}
return Storage::disk('local')->download($path);
ほかにも、書き込み(put など)が失敗しても例外ではなく false が返るようになり、既存ファイルは標準で上書きされます。従来どおり失敗時に例外を出したい場合は、config/filesystems.php の各ディスクに 'throw' => true を追加します。S3を使っている場合は composer require -W league/flysystem-aws-s3-v3 "^3.0" で対応する部品を入れ直します。
メール送信(SwiftMailer → Symfony Mailer)
メール送信の部品SwiftMailerは開発が終了したため、Symfony Mailerに入れ替わりました。通常の Mail::to()->send() はそのまま動きますが、SwiftMailer固有の機能を使っている箇所は書き換えが必要です。
// Laravel 8 まで
$this->withSwiftMessage(function ($message) {
$message->getHeaders()->addTextHeader('X-Order-Id', $this->order->id);
});
// Laravel 9 以降
use Symfony\Component\Mime\Email;
$this->withSymfonyMessage(function (Email $message) {
$message->getHeaders()->addTextHeader('X-Order-Id', (string) $this->order->id);
});
Mailgun・Postmarkなど外部のメール配信サービスを使っている場合は、対応する部品(symfony/mailgun-mailer など)の追加も必要です。メールは「送れたように見えて届いていない」が起きやすいので、検証環境で実際に受信して確認しましょう。
そのほか、Laravel 9で確認すること
- 入力チェックの
passwordルールはcurrent_passwordに改名 - 入力チェックで、ルールを書いていない配列のキーは
validated()の結果から除かれるようになった - 外部APIを呼ぶHTTPクライアントに、標準で30秒のタイムアウトが付いた(時間のかかる連携があれば個別に延長)
- テストの
assertDeletedはassertModelMissingに変更 - CORS(=別ドメインからのアクセス許可)に
fruitcake/laravel-corsを使っている場合は、Laravel本体に取り込まれた仕組みへの移行を検討
PHPを8.1に切り替える
Laravel 9でテストが通ったらPHPを8.1に上げます。PHP 8.1で影響が大きいのは、PHPの内部関数に null を渡すことが非推奨になった点 です。フォームの任意項目が未入力のときに起きがちです。
$note = trim($request->input('note')); // 未入力(null)だと PHP 8.1 で非推奨の警告
$note = trim((string) $request->input('note')); // 文字列に変換してから渡す
非推奨の警告は、放置すると次のPHPで本当に動かなくなる予告です。Laravel 9の新しい設定ファイルには、非推奨の警告をログに出すための deprecations という設定があります。既存プロジェクトの config/logging.php には入っていないため、新しいLaravelの設定ファイルを参考に追加し、.env の LOG_DEPRECATIONS_CHANNEL でログに出して洗い出しましょう。
ステップ4:Laravel 9 → 10、PHPを8.2へ
📰 出典:Laravel 10.x Upgrade Guide
部品のバージョンを上げる
"php": "^8.1",
"laravel/framework": "^10.0",
"laravel/ui": "^4.0",
"spatie/laravel-ignition": "^2.0",
"nunomaduro/collision": "^7.0",
"phpunit/phpunit": "^10.0"
composer.json の minimum-stability は stable にします(または削除)。Composer自体も2.2以上が必要です。PHPUnit 10への更新は任意ですが、上げる場合は phpunit.xml の <coverage> から processUncoveredFiles 属性を削除します。
日付項目の $dates を $casts に書き換える
古いLaravelでよく使われていた $dates プロパティが削除されました。残っていると日付が文字列のまま扱われ、日付の計算や表示形式の指定でエラーになります。
// Laravel 9 まで
protected $dates = ['ordered_at', 'shipped_at'];
// Laravel 10 以降
protected $casts = [
'ordered_at' => 'datetime',
'shipped_at' => 'datetime',
];
そのほか、Laravel 10で確認すること
Bus::dispatchNow()・dispatch_now()は削除 →dispatchSync()・dispatch_sync()へ- テストの
expectsEvents・expectsJobsなどは削除 →Event::fake()・Bus::fake()へ DB::raw()の結果を(string)で文字列にしている箇所は書き換えが必要- ログの部品Monologが3系に。外部のエラー監視サービスと連携している場合は、その部品も対応版へ
PHPを8.2に切り替える
PHP 8.2では、クラスに宣言していないプロパティへの代入(動的プロパティ)が非推奨 になりました。Eloquentモデル(=DBの1行を表すクラス)は対象外ですが、自作のクラスで使っていると警告が出ます。
class InvoiceBuilder
{
public int $total = 0; // 使うプロパティを宣言しておく
}
$builder = new InvoiceBuilder();
$builder->total = 50000; // 宣言していないプロパティへの代入は PHP 8.2 で非推奨
文字列の中で "${name}" と書く変数展開も非推奨になったため、"{$name}" に書き換えます。
ステップ5:Laravel 10 → 11、PHPを8.3へ
📰 出典:Laravel 11.x Upgrade Guide
部品のバージョンを上げる
"php": "^8.2",
"laravel/framework": "^11.0",
"nunomaduro/collision": "^8.1"
Sanctum(API認証)・Passport・Telescopeなど公式の部品を使っている場合は、それぞれ新しいメジャーバージョンに上げ、php artisan vendor:publish --tag=sanctum-migrations のように部品のマイグレーションをアプリ側に取り込みます。
新しいフォルダ構成には「移行しない」
Laravel 11では、新規プロジェクトのフォルダ構成が大きく簡素化されました(設定ファイルやミドルウェアのファイルが減った)。ただし公式のアップグレードガイドは、既存のアプリを新しい構成に移行することを推奨しないと明記しており、Laravel 11は従来の構成でもそのまま動くように作られています。見た目を新しくするための作り替えは、工数とリスクを増やすだけなので避けましょう。
マイグレーションの change() の挙動が変わる
影響が大きいのが、既存の列を変更する change() の挙動です。Laravel 11からは、変更後も残したい属性(unsigned・初期値・コメントなど)をすべて書き直さないと、書かなかった属性が消えます。
// 以前のマイグレーションで unsigned・default・comment を付けた列を、nullable に変える場合
Schema::table('order_items', function (Blueprint $table) {
$table->integer('quantity')
->unsigned()
->default(1)
->comment('数量')
->nullable()
->change();
});
これから書くマイグレーションだけでなく、テストのたびに全マイグレーションを流し直している場合は、過去の change() も新しい挙動で実行される点に注意が必要です。過去のマイグレーションを書き直したくない場合は、php artisan schema:dump で現在のDB構造を1つのファイルにまとめる方法が公式に案内されています。
そのほか、Laravel 11で確認すること
double・float型の列の作り方が変わり、桁数の指定方法が変わったdoctrine/dbalが不要になった(入れている場合は削除できる)- ログイン時に、パスワードの暗号化の強さが変わっていれば自動で再暗号化される(パスワードの列名が
password以外なら設定が必要) - アクセス回数の制限(レート制限)が秒単位に対応し、関連クラスの引数が「分」から「秒」に変わった
- 日付ライブラリCarbonは2系・3系の両方に対応(3系への移行は次の段階で必須になる)
この段階でPHPを8.3に上げておくと、次の段階以降(Laravel 12・13)の要件をまとめて満たせます。
ステップ6:Laravel 11 → 12、PHPを8.4へ
📰 出典:Laravel 12.x Upgrade Guide
部品のバージョンを上げる
"php": "^8.2",
"laravel/framework": "^12.0",
"phpunit/phpunit": "^11.0"
Laravel 12は公式に「多くのアプリはコードを変えずに上げられる、依存部品の更新が中心のリリース」と説明されており、ここまで来れば変更点は多くありません。
Carbon 3への対応
Laravel 12からは日付ライブラリCarbonの3系が必須です。影響が出やすいのは、日付の差を求める diffIn〜 系のメソッドが 小数を返すようになり、日付の前後関係によってはマイナスの値を返す 点です。「受注から何日経過したか」で処理を分けている箇所は要確認です。
$days = $order->ordered_at->diffInDays(now()); // Carbon 2: 3 / Carbon 3: 3.42…(小数)
$days = (int) $order->ordered_at->diffInDays(now()); // 整数で扱いたい場合は明示的に変換する
そのほか、Laravel 12で確認すること
HasUuidsで作られるIDが、UUIDの新しい形式(v7)になった。従来の形式を保つならHasVersion4Uuidsを使うconfig/filesystems.phpにlocalディスクを定義していない場合、保存先がstorage/app/privateに変わる(Laravel 6時代の設定ファイルを使い続けていれば、通常は定義済み)- 入力チェックの
imageルールが、標準ではSVG画像を受け付けなくなった
PHPを8.4に切り替える
PHP 8.4では、初期値に null を指定した引数の型に ? を付けない書き方が非推奨 になりました。古いコードによくある書き方なので、Rectorでまとめて直すと効率的です。
public function index(Request $request, Customer $customer = null) // PHP 8.4 で非推奨
public function index(Request $request, ?Customer $customer = null) // 型の前に ? を付ける
📰 出典:PHP 8.3.x から PHP 8.4.x への移行(PHPマニュアル)
仕上げ:本番への切り替えと戻し方を決めておく
コードの準備ができても、本番の切り替えで失敗しては意味がありません。リリースの前に次の点を決めておきます。
- サーバー側のPHPの切り替え方法:サーバーに直接PHPを入れているのか、コンテナで動かしているのか。レンタルサーバーなら、管理画面で選べるPHPのバージョンも確認します。
- PHPの設定と拡張機能:
php.iniの設定(メモリ上限・アップロードサイズなど)や、使っている拡張機能(画像処理・PDF・Excel出力など)が新しいPHPでも入っているか。 - 戻し方(ロールバック):問題が起きたときに、旧バージョンのコードとPHPに戻す手順。今回の手順ではDBの構造はほとんど変わりませんが、部品のマイグレーションを取り込んだ段階(Sanctumなど)は戻し方も確認しておきます。
- リリース後の監視:エラーログと非推奨の警告ログを、数日間は重点的に確認します。
画面の見た目を作る部分(JavaScript・CSS)は、Laravel 6時代のLaravel Mixのままでも動かせます。新しい仕組み(Vite)への移行は、PHPとLaravelのバージョンアップとは 別の作業として分ける と、問題の切り分けがしやすくなります。ただし、古いLaravel Mixが新しいNode.jsで動かないことがあるため、ビルド(=画面用ファイルの生成)に使うNode.jsのバージョンは事前に確認しておきましょう。
自動化ツールやAIは「機械的な修正」に使い、確認は人とテストで
バージョンアップの作業を助けるツールもあります。
| ツール | できること |
|---|---|
| Rector | PHPやLaravelのバージョンに合わせて、コードの書き方を自動で書き換える(Laravel向けのルール集もある) |
| PHPStan / Larastan | コードを実行せずに、型の不整合や存在しないメソッドの呼び出しを見つける |
| Laravel Shift | Laravelのバージョンアップ作業を自動化する有償のサービス |
| AIコーディングツール | アップグレードガイドに沿った書き換え案の作成や、影響箇所の洗い出しを補助する |
どのツールも、機械的に直せる部分を減らすものです。日付の出力形式やファイルが無いときの動きなど、「書き方は正しいが業務上の意味が変わる」変更は、ツールだけでは見つけられません。最終的な確認は、テストと人による画面確認で行いましょう。
Laravel 12で止めずに、13まで視野に入れる
ここで1つ大事な注意点があります。Laravel 12のセキュリティ修正は2027年2月24日までです。2026年10月時点では、残りおよそ4か月しかありません。すでにLaravel 13(2026年3月リリース、PHP 8.3以上、セキュリティ修正は2028年3月17日まで)が公開されています。
この記事の手順でPHPを8.4まで上げていれば、Laravel 13の動作条件は満たしています。Laravel 13も公式に「変更点が少ないアップグレード」と説明されているため、12は通過点として、同じ計画の中で13まで上げることを検討してください。12→13で主に確認するのは、CSRF対策(=なりすまし送信を防ぐ仕組み)のミドルウェア名の変更、PHPUnit 12への更新、キャッシュやセッションに保存するデータの扱いの強化などです。せっかく大がかりな作業をしても、数か月後にまたサポート切れになってしまっては、発注者として納得しにくいはずです。
📰 出典:Laravel 13.x Upgrade Guide
期間・工数が膨らむ条件(発注者向け)
同じ「Laravel 5・6から12へ」でも、システムによって工数は大きく変わります。開発会社の見積もりを見るときは、次の条件に当てはまるかを確認すると、金額や期間の理由を理解しやすくなります。
| 条件 | 工数が膨らむ理由 |
|---|---|
| 元のバージョンが古い(Laravel 5系以前) | 上げる段階の数が増え、各段階の確認の手間も増える |
| 自動テストがほとんど無い | テストを書く作業、または毎段階の画面確認の手間が増える |
| 外部パッケージが多い・開発が止まった部品がある | 代わりの部品への置き換えと、その動作確認が必要 |
| 部品のソースを直接書き換えている | 部品を上げると独自の修正が消えるため、作り直しが必要 |
| メール・ファイル・外部APIとの連携が多い | Laravel 9での部品の入れ替えの影響範囲が広い |
| 画面がJavaScriptで作り込まれている | 日付形式の変化やビルド環境の更新の影響を受けやすい |
| 本番環境が古いOSや共用サーバー | PHPを上げるためにサーバーの移行が必要になることがある |
| 検証環境が無い・本番と構成が違う | 検証環境づくりから始める必要がある |
フレームワークの更新費用が高く見えるときの確認ポイントは、次の記事でも解説しています。


やりがちな失敗
composer.jsonを一気にLaravel 12へ書き換える:エラーが大量に出て、どの変更が原因か分からなくなります。1世代ずつ進めましょう。- PHPだけ先にサーバーで上げてしまう:Laravel 5系はPHP 8に、Laravel 6はPHP 8.1以上に対応していないため、システムが動かなくなります。PHPの切り替えは必ずLaravelの後です。
- テストを書かずに画面を目で見るだけで進める:段階が多いため、確認漏れが積み重なります。主要な流れだけでもテストにしましょう。
- Laravel 11で新しいフォルダ構成に作り替える:公式も推奨しておらず、工数とリスクが増えるだけです。
- 上げて終わりにする:毎年メジャーバージョンが出るため、次の更新をいつ・誰が・どの費用で行うかを決めておかないと、数年後に同じ状況になります。
発注者がやること チェックリスト
- ☐ 今のLaravelとPHPのバージョン、それぞれのサポート期限を開発会社に確認した
- ☐ 段階的なバージョンアップか作り直しか、理由とあわせて提案を受けた
- ☐ 最終的な目標バージョン(Laravel 12で止めるのか、13まで上げるのか)を決めた
- ☐ 開発が止まった外部パッケージの有無と、置き換えの要否を確認した
- ☐ 検証環境での確認方法(自動テスト・画面の確認表)を共有してもらった
- ☐ 本番に反映する回数・時期・停止時間と、問題が起きたときの戻し方を決めた
- ☐ 業務の担当者が確認すべき画面・帳票・メールの一覧を用意した
- ☐ 次回以降のバージョンアップの頻度と費用の扱い(保守契約に含むか)を決めた
開発会社への質問例
- 「今のLaravelとPHPで、PHPを8.2以上に上げるためには、Laravelをどこまで上げる必要がありますか?」
- 「段階的なバージョンアップと作り直しでは、期間・費用・リスクはどう違いますか?おすすめする理由も教えてください」
- 「Laravel 12のセキュリティ修正は2027年2月で終わると聞きました。同じ計画の中でLaravel 13まで上げることはできますか?」
- 「使っている外部パッケージの中に、Laravel 12や13に対応していないものはありますか?ある場合はどう対応しますか?」
- 「各段階の動作確認は、自動テストと画面確認のどちらで、どの範囲を行いますか?こちらで確認すべき画面はどれですか?」
まとめ
Laravel 5系はPHP 8に対応しておらず、Laravel 6も対応はPHP 8.0までです。Laravel 5・6もPHP 8.0も、すでにセキュリティ修正が終わっています。PHPの脆弱性に対応するためにPHPを上げるなら、Laravelも上げる必要があります。
標準的なMVC構成のLaravelアプリなら、作り直さなくても、Laravelを1世代ずつ(5系なら5.5→5.6→5.7→5.8→6、その後6→7→8→9→10→11→12)、PHPを階段状に(7.1→7.2→7.4→8.0→8.1→8.2→8.3→8.4) 上げていく方法で対応できることが多いです。Laravel 5系では、キャッシュの有効期限の単位の変化(5.8)や文字列・配列ヘルパーの削除(6)が要注意です。Laravel 6以降で特に変更が多いのは、ファクトリの作り替え(Laravel 8)、ファイル保存とメール送信の部品の入れ替え(Laravel 9)、マイグレーションの書き方の変化(Laravel 11)、日付ライブラリの変化(Laravel 12)です。
そして、Laravel 12のセキュリティ修正は2027年2月24日までです。バージョンアップの計画を立てるときは、12をゴールにせず、13まで、さらに次の更新の進め方まで含めて開発会社と相談してみてください。
あわせて読みたい関連記事












コメント
コメント一覧 (1件)
[…] […]