メシのタネ

メシのタネになる、Laravelや設計思想の技術配信サイト


Laravel6→12 負債博物館を現代基盤へ移行してみた


  1. Laravel
  2. Laravel6→12 負債博物館を現代基盤へ移行してみた

練習の一貫として、負債資源を一旦別Repositoryに切り出して基盤をLaravel6から12に一気にアップグレードしたら何が起こるんだろうという好奇心からやってみた。

環境差によって対処法が違うので、手順とかを詳細を書くことで逆にハマりを誘発しそうなので、(そのままコピーして環境が壊れてしまったら怖いので)、実際何を後悔して、どう悩んで、どういう選択をしたかっていうところを共有できたらな思います。

方針を示したりする補助的な意味合いでコードを明示することはありますが、取り扱いは慎重にお願いします。

レガシーシステムのLaravel基盤のBefore、After

項目現状目標一言
PHP7.28.4EOLなのに稼働中。怖。
Laravel6.212.19.36世代落ち。泣ける。

Dockerは Apache 単騎。DBも Redis もいない。

# 現状:最小限のApacheコンテナのみ
version: '3'
services:
  apache:
    build: ./docker/apache
    ports:
      - "8080:80"

こんな感じでEC2にStage環境を都度準備するという面倒な運用してた。

依存関係の化石度

{
  "php": "^7.2",                    
  "laravel/framework": "^6.2",      
  "fzaninotto/faker": "^1.4"      
}
  • 2022年くらいにPHPもLaravel6.2もEnd Of Life💀
  • このFakerも別パッケージに移行済み

プロジェクト構造の特徴

  • モデル数: 14個(見積・請求書系中心)
  • コントローラー: 15個以上
  • 独自実装: 謎コンポーネント群
  • テストコード: 皆無 🚨

技術的負債の山

  1. 独自認証プロバイダー(このときはイケてると思ってた。なるべく標準使ったほうが良い!)
  2. 独自Facade実装が名前だけで地雷臭(CommonXX、, GetConfig, SessionUtil)
  3. 設定ファイルベースConfig::get('routes')のルーティング(マルチテナントを最初考えてた残骸を処分してない)

Laravel6から12への移行戦略

段階的移行は捨てて一気に最新版まで持っていく。
大胆だとは思いますが、既存プロジェクトコンテナ化すればこの戦略でやれそうな気もする。
もちろんProdするまでに、ちゃんとリグレッションテストは必要ですが。

Phase 1: 環境基盤の刷新
1. PHP 7.4 → 8.4へのアップグレード(Docker環境)
2. composer.jsonの全面書き換え
3. 新Laravel 12構造への適応
Phase 2: 破壊的変更への対応
1. 名前空間の移行(Models、Seeders)
2. 廃止パッケージの置換(例:fzaninotto/faker → fakerphp/faker)
3. 独自実装部分の互換性確認
Phase 3: 動作確認と調整
1. 基本動作の確認
2. 外部連携の動作確認
3. 独自コンポーネントの調整
たねまる

既存基盤をGOALに据えてGO!何が起きるかな〜!

Laravel6から12へのアップグレードで想定してたこと

1. PHP 7.2 → 8.4

  • 型システムの厳格化
    • 型宣言なしのコードで型エラーが頻発(特にnullableまわり)
    • 古いライブラリや独自クラスでint|nullのような明示的記述がなくてClash
    • 自動キャスト(例:文字列 ’10’ を int に自動変換)に依存していた処理が死ぬ。
  • 非推奨機能の削除
    • each()、create_function()、assert(string)などのレガシーコード依存
    • implode()の引数順(非標準のまま書いてると警告→エラーへ)。
  • エラーハンドリングの変更
    • ErrorとExceptionの取り扱いがより厳密に(catch(Throwable)でないと漏れる)

2. Laravel 6世代からの大幅変更

  • app/Modelsディレクトリへの移行
    • PSR-4のautoloadが想定外に狂う(App\User → App\Models\User へ)
    • 既存のコードが全体的にApp\Userで参照していてリファクタ必須。
  • ルーティング設定の変更
    • $router-> vs $route-> の記法揺れ
    • Route::resource()のカスタマイズ方法の変化

PHP 8.4環境構築

Dockerfileの刷新

まずはPHP環境から一気に最新化。

FROM php:7.4-apache-buster
COPY --from=composer:2.0.11 /usr/bin/composer /usr/bin/composer

変更後

FROM php:8.4-apache
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

主な変更点

  • PHP 7.4 → 8.4
  • Composer 2.0.11 → latest
  • パッケージインストールの最適化(一括処理)
  • PHP拡張にzip、intlを追加

Docker構成の現代化

Apacheはもう古い。今風のLaravel環境にしたかった。

新構成案

  • PHP-FPM + Nginx (軽量・高速)
  • MySQL 8.0
  • Redis (セッション・キャッシュ)
  • Node.js 20 (Vite用)

Apache単体 → マルチコンテナ構成で現代的に。

services:
  apache:
    build: ./docker/apache/Dockerfile
    ports: ["8080:80"]

After(マルチコンテナ) – 長いので雰囲気だけ

services:
  nginx:      # リバースプロキシ
  php:        # PHP-FPM 8.4
  mysql:      # MySQL 8.0
  redis:      # キャッシュ・セッション
  node:       # Vite開発サーバー

composer.json 大手術

依存関係の刷新

🔥 Before → After

パッケージBeforeAfter変更理由
PHP^7.2^8.4Laravel推奨似合わせる
Laravel^6.2^12.19.3基盤を最新にしたい
PHPUnit^8.0^11.0最新テストフレームワーク
Fakerfzaninotto/fakerfakerphp/faker開発終了→移行
Ignitionfacade/ignitionspatie/laravel-ignitionLaravel 11+対応

新規追加パッケージ

  • Laravel Pint (コードフォーマッター)
  • Laravel Breeze (認証スカフォールド)
  • Laravel Sail (Docker開発環境)
  • Predis (Redis接続)

削除パッケージ

  • fideloper/proxy → Laravel 11で統合済み
  • doctrine/dbal → Laravel 11で不要

Laravel6の構造を12で動くように変更する

Modelディレクトリの移行

  • 現在: app/User.php (Laravel 6構造)
  • 目標: app/Models/User.php (Laravel 8+標準)

名前空間変更: App\App\Models\

全参照の更新: コントローラー、シーダー、ファクトリー

シーダーディレクトリの移行

  • 現在: database/seeds/
  • 目標: database/seeders/

名前空間追加: Database\Seeders

composer.json: classmapからPSR-4へ変更済み

設定ファイルの更新

  1. config/app.php: プロバイダー構成の見直し
  2. .env.example: Laravel 12対応項目の追加
  3. routes/: 名前空間設定の調整

作業順序

  1. app/Models/ ディレクトリ作成
  2. モデルファイルの移動と名前空間更新
  3. 全ファイルでの参照更新
  4. シーダー・ファクトリーの移行

Laravel12環境起動まで

  • appleシリコンMacだったため、少しハマる

問題

  • MySQL: ARM64対応でplatform指定不要
  • Redis/PHP: 起動が非常に遅い(タイムアウト)
  • Node: package.jsonが存在しないためエラー

MySQLの問題

一旦arm64v8/php:8.3-fpm-alpine これで対応したけど、これは過渡期対応だったらしく、2025/7/1現在は公式使えば問題なさそう

# After: 公式multi-archイメージ  
mysql:
  image: mysql:8.4  # LTS版
  # platform指定不要 - 自動でARM64を選択

PHPの問題

まずRedisはfileで要件足りるのでRedisは一旦使わないことにした。

依存関係制御

mysqlが健康状態になるまで起動を待つように修正

  php:
    depends_on:
      mysql:
        condition: service_healthy

イメージの最適化

こっちはx86-64イメージを使ってたのがネックでこれを使うようにしたphp:8.4-fpm-alpine

公式Multi-archイメージの利点

  • 自動アーキテクチャ判定: Intel Mac/M1 Mac両対応
  • メンテナンス性: arm64v8/*は非公式、更新遅延リスク

Alpineの軽量メリット

  • Debian系php:8.4-fpm 約500MB
  • Alpine系 php:8.4-fpm-alpine 約80MB
たねまる

Laravelはmuslで十分だけど、他にレガシーシステム積んでるならglib入ってないからAlpine使う場合は要注意だね〜。

Laravel12を入れたあとに諸々出た不具合

とりあえず、83パッケージ更新して、Laravel/framework v12.9.3 になった後に出た問題を列挙。

PSR-4準拠エラー

最後がdoes not comply with psr-4 autoloading standard がいくつか出てた。原因は下記のような事象。

  • ファイル名とクラス名の不一致
  • 名前空間の大文字小文字ミス (App\models\App\Models\)

bootstrap/app.php 古すぎ問題

Fatal error: Class "Illuminate\Foundation\Application" not found

が出てる。調べてみると原因は

Laravel 6時代のbootstrap/app.phpでLaravel 12を起動しようとしていたこと

Before (Laravel 6)

$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
// kernelバインディング...

After (Laravel 12) これに書き換え

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {})
    ->withExceptions(function (Exceptions $exceptions) {})
    ->create();

これでこのエラーは解消

Laravel8で、ルーティングの名前空間自動付与が廃止

Laravel8から名前空間の自動付与が終わりました。

Before (Laravel 6)

// config/routes.php
'callback' => 'LoginController@login'  // 自動で App\Http\Controllers\ が付与

After(Laravel8+)

// 完全な名前空間が必要
LoginController@index -> App\Http\Controllers\LoginController@index

IDE補完効かせたいなら

Route::get('/',[App\Http\Controllers\LoginController::class, 'index'])

ルーティングについての詳しい説明は
👉️ 【Laravel入門】ルーティングの基本と実践活用まとめ:仕組み・書き方・落とし穴まで

LoginController@index とりあえず、これを一個ずつちまちま名前変更するの大変なので、sed -i とか使って一撃で変更するのが良いかもです。

AuthenticatesUsersの廃止

ログイン機能が独自実装なので使ってなかったのですが、useしてたので、これをコメントアウトしました。

とはいえこんな感じ。

Trait が担当していた機能独自実装でカバー
入力バリデーション✅ 自前 FormRequest (HogePost)
Auth::attempt() + セッション再生成✅ hoge_login() 内で実装
Throttle / Remember Me❌ 使っていない(実装も無し)
ログアウト後トークン再生成✅ hoge_logout() で実装

偶然にも必要分だけ自前コードがあった ため、Trait を消しても動作は維持。

自前じゃなかったらどうするか

もし自分がTraitを使っていたとしたら、Auth::attempt() でログインした箇所の実装を進めないといけなくなると思います。

Blade構成だからcsrfをView側にもたせてミドルウェアにVerifyCsrfToken が登録されてる前提です。

// routes/web.php  ← web ミドルウェアに乗る=VerifyCsrfToken が働く
Route::view('login', 'auth.login')->name('login');

Route::post('login', function (Illuminate\Http\Request $req) {
    $cred = $req->validate([
        'email'    => ['required','email'],
        'password' => ['required'],
    ]);

    if (Auth::attempt($cred, $req->boolean('remember'))) { // login処理
        $req->session()->regenerate();   // セッション固定攻撃防止
        return redirect()->intended('/');
    }

    return back()->withErrors(['email'=>'認証失敗'])->onlyInput('email');
})->name('login.post');

Route::post('logout', function (Illuminate\Http\Request $req) {
    Auth::logout();
    $req->session()->invalidate();
    $req->session()->regenerateToken();  // CSRF トークンも更新
    return redirect('/');
})->name('logout');

Blade側はこんなような構成を想定してます、

// brade側
<!-- resources/views/auth/login.blade.php -->
<form method="POST" action="{{ route('login.post') }}">
    @csrf <!-- ★ これがないと 419 (Page Expired) -->
    <input type="email"   name="email"    placeholder="Email">
    <input type="password" name="password" placeholder="Password">
    <label><input type="checkbox" name="remember"> Remember me</label>
    <button type="submit">Login</button>
</form>

ポイント

  • @csrf を忘れると CSRF ミドルウェアが 419 を返す
  • Remember Me を使うなら users.remember_token 列を追加

自分がもしレガシーシステム移行で、ログイン機能を自前実装しておらず、アップグレードの影響をモロに受けたらどうするかなーと考えてみました。

おわりに

正直この作業自体、別リポジトリでやるからどうなっても良いって前提作ってからやるってとこで、中々面白い作業だと思いました。
作業自体を記事にしようと思っていざ記事にしてみると、ハマったところを上手く言いたすぎて中々上手く仕上がらないなというのが正直なところです。
一連作業をあらかじめ体感しておくことで、実際自分が任された時に、どこでハマりそーかな~って目星つけられるのがいいとこかなと思いました。

というわけでLaravel12移行が済んだところでいくつか記事紹介します。

👉️ Laravel12の新機能を学びたい人向けの記事

👉️ Laravelオススメ構成で12を始めて対人はLivewire Kit入門


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください