PHPレガシーのリスクはポインタではなく、老朽化した実行環境、暗黙の型変換、権限チェック、そしてDBや外部連携に潜んでいます。ほとんどの場合、正解はPHPを捨てることではなく、PHPをサポート対象のバージョンに上げ、検証とデプロイの仕組みを先に整えることです。

チョン・ドヒョン - ROBOCO首席コンサルタント


第1部では、20年以上前のC/C++アプリケーションをバイブコーディングでモダナイズする方法を扱いました。1 今回は、レガシーWeb、なかでもPHPを扱います。

PHPは1995年に登場し、Webの歴史とほぼ同じ時間をともに歩んできました。その間に数多くの企業サイト、ECサイト、コミュニティ、社内業務システムがPHPで作られ、その相当数はいまも稼働しています。韓国国内だけでも、GnuboardやYoungcartで作られたサイト、自作のPHP5管理画面、古いLaravelやCodeIgniterのプロジェクトに、コンサルティングの現場でよく出会います。こうしたプロジェクトでも「エージェントがきちんと仕事をしてくれない」という声を聞きます。

第1部の結論は、PHPにもそのまま当てはまります。エージェントが失敗する主な理由は、自分が行った変更が正しいかを確かめる手段がないことです。だからこそ、モダナイゼーションの最初の投資先は検証環境であるべきです。しかし、何を検証すべきかはC/C++とは異なります。本記事では、2026年10月16日時点で調査した公開事例・公式サポートポリシー・ツールドキュメントをもとに、PHPに合ったモダナイゼーション戦略を整理します。事例の数値はすべて発表主体による自己報告であり、私が独自に再現したものではありません。

シリーズ:バイブコーディングでレガシーをモダナイズする

  1. 20年もののC/C++アプリケーション
  2. Webの歴史とともに積み重なったPHP(本記事)

TL;DR

  • PHPレガシーの主なリスクはポインタの寿命ではなく、サポートが終了した実行環境、動的型付けと暗黙の動作、グローバル状態、認証・認可、DBと外部連携、そしてテストとデプロイの仕組みの欠如です。
  • ほとんどのプロジェクトには、PHPを維持しながらランタイムと依存関係をサポート対象のバージョンに上げ、検証・デプロイの仕組みを作ったうえで、必要な部分だけを再構成する方法が適しています。
  • ルールが明確な大量変換はRectorのような決定的なツールに任せ、エージェントは調査・残った例外の修正・テスト作成を担当します。人は業務上の動作・権限・データ・切り替えのリスクを判断します。
  • Webプロジェクトの同等性は、画面が表示されることでは確認できません。HTTPリクエスト、ユーザー権限、セッション、DBの変更、メール・決済のような外部効果を1つのシナリオとして比較する必要があります。
  • 公開事例では、エージェントが権限チェックの欠落(IDOR)、メール送信の欠落、+1のような定数補正、DBカラムをnullableに変える回避策を実際に作り出しました。こうした失敗を捕捉する検証基準を先に立てる必要があります。

1. PHPレガシーはC/C++レガシーと何が違うのか

C/C++レガシーの核心的な難題は、ポインタの所有権とオブジェクトの寿命でした。PHPはリクエストが終わるとメモリが解放される実行モデルなので、この問題はほとんどありません。そのかわり、別のところで問題が起きます。

区分 C/C++レガシー PHPレガシー
主なリスク ポインタの所有権、オブジェクトの寿命、メモリ欠陥 実行環境の老朽化、暗黙の型変換、権限、DB・外部への副作用
事実上の仕様 オリジナルのバイナリの動作とABI 稼働中のサーバーのコード、DBスキーマとデータ、URL
同等性の比較単位 関数・ライブラリの入出力 HTTPリクエスト、ユーザーロール、セッション、DBの変更、外部効果
代表的な決定的ツール clang-tidy、LibTooling、C2Rust Rector、PHPStan、PHPCompatibility、Composer
よくある誤った目標 すべてをRustに翻訳 すべてを最新フレームワークとSPAで書き直し

私が現場で見てきたPHPレガシーで、エージェントの作業ループが途切れるポイントはおおむね4つです。

1つ目に、リポジトリのコードが本番のコードと異なります。 FTPでサーバー上で直接修正したファイル、ベンダーやCMSのコアを直接書き換えた部分、Gitに入っていない設定ファイルはよくあります。エージェントがリポジトリだけを読んで作業すると、実際には稼働していないコードを基準に判断してしまいます。

2つ目に、実際の実行環境を再現するのが困難です。 WebサーバーのPHPとcronが使うCLIのPHPとで、バージョンやphp.iniが異なることがあります。サポートが終了したPHP 5.xと古い拡張モジュール、EUC-KRのような文字エンコーディング、特定のMySQL設定に動作が依存しているケースも多くあります。

3つ目に、コード検索だけではエントリーポイントをすべて見つけられません。 URLから直接呼び出されるPHPファイル、動的なinclude、eval、DBに保存されたSQLやルールがあると、シンボル検索では呼び出し関係が見えてきません。そのため、アクセスログとcronのスケジュールもあわせて確認する必要があります。

4つ目に、DBと外部連携が事実上の仕様です。 ORMのモデルクラスが実際のDBスキーマと異なるケースは多くあります。実際のDDL、インデックス、trigger、データの分布が業務ルールを担っています。メール・SMS・決済(PG)連携は、テスト中に実際に実行されてしまうと取り消せません。

2. 公開事例が示すこと

PHPのモダナイゼーション事例はC/C++より多くあります。ただし、単純なAI支援コーディング、ツールを直接実行するエージェント、ルールベースの自動変換が混在しているので、区別して読む必要があります。

事例 元のシステムと目標 ツール 発表された結果 適用上の限界
DVHARDWARE2 2002年から積み重なった手続き型PHPのCMS → PHP 8.5とレイヤー構造 Codex、Docker、PHPStan、Deptrac 初期の機能対応版を3晩の作業で確保し、その後も改善を続けてデプロイ 比較的単純なCRUDのCMSであり、機能の同等性は運営者自身の判断
Progrit3 7つのリポジトリ、PHP 8.2→8.4、Laravel 10→12 Claude Code、Rector 通常の開発と並行して大きな問題なく完了したと報告 期間・コスト・自動化率は非公開
Rocket4 PHPの画面・ロジック → Angular中心の構造への残りの移行 Claude Code 教育を含めて4か月、シニアエンジニアの作業処理量が82件→201件 組織の変化もあわせて反映された数値であり、PHPサーバー全体の置き換えの根拠ではない
Boldare5 Symfony 5.4→6.4 Claude 数日と見込んでいた作業を数時間で実施 包括的な既存テストがあった。プロジェクト規模は非公開
2am.tech6 古いLaravelアプリ → 新しいLaravel APIとReact GitHub Copilotエージェント 垂直な機能単位で移植しながら、権限・メール・状態の欠陥を発見・修正 期間と生産性の数値は非公開
FuelPHP→Laravel7 40万行以上、2千以上のファイル ユーザー定義のRectorルール約80個 PHPファイルの99%を自動変換 2021年の作業でありLLMの事例ではない。約11か月にわたるQAとcanaryが必要

この表でもっとも学ぶべき点は、成果の数値ではなく、公開された失敗です。

DVHARDWAREの運営者は、PHP-Nukeから始まり24年間修正されてきたサイトをモダナイズしました。まずコードをGitに入れ、DockerとDBのコピーでローカルの実行環境を作ったうえで、新旧バージョンを並行させながら移行しました。ところがエージェントは、ユーザー数の計算が合わないと、原因を直すかわりに結果に+1を足して数字を合わせました。オリジナルの誤った前提や不要なレイヤーをそのまま移植することもありました。運営者は、生成されるコードの量が多いためにレビューが甘くなるという問題も報告しています。

2am.techの事例は、さらに深刻な失敗を公開しています。メールテンプレートは移植したものの、実際の送信呼び出しが接続されていませんでした。JWTのフローで欠けていたフィールドを解決するかわりに、DBカラムをnullableに変えて回避しようとしました。ユーザーIDをパラメータとして受け取るAPIで所有権チェックが抜け落ち、他のユーザーのデータにアクセスできるIDOR(Insecure Direct Object Reference)脆弱性が生まれました。一部の書き込みAPIは一般ユーザーにも開放されていました。

これらの失敗から3つの教訓が得られます。1つ目に、UIでボタンを隠すことと、サーバーで権限をチェックすることは別物です。2つ目に、画面が表示されてHTTP 200が返っても、メール・DB・イベントの接続が抜けている可能性があります。3つ目に、ORMのモデルから推測したスキーマを実際のDBスキーマとして扱ってはいけません。2am.techのチームも、作業単位をDTO・コントローラーのようなレイヤー別の作業から route→controller→service→repositoryを貫く垂直な機能単位 に変えてはじめて、こうした欠落を体系的に捕捉できるようになりました。

成功事例にも共通点があります。Boldareでは、包括的な既存テストが修正のたびにすぐフィードバックを返していました。Progritは、公式の変更ドキュメントで影響範囲と修正方針を先に整理し、その内容を共通プロンプトにして7つのリポジトリに適用し、単純な繰り返し作業はRectorに任せました。つまり、テストがすでにあり、変更範囲が限定されたプロジェクトでは素早く成果が出ます が、テストのない手続き型PHP5のプロジェクトで同じスピードを期待することはできません。

3. 目標バージョンから決める

モダナイゼーションの目標バージョンは、公式のサポートポリシーで決めます。以下は、2026年10月16日にPHP公式サイトで確認したサポートスケジュールです。8

PHPバージョン アクティブサポート終了 セキュリティサポート終了 新規モダナイゼーションプロジェクトでの解釈
8.2 2024-12-31 2026-12-31 セキュリティサポートの終了が目前のため、最終目標には不適
8.3 2025-12-31 2027-12-31 制約がある場合の中間目標
8.4 2026-12-31 2028-12-31 プラグイン・拡張の互換性しだいで現実的な選択肢
8.5 2027-12-31 2029-12-31 依存関係のサポートが確認できれば優先的に検討すべき目標

フレームワークも同じ方法で判断します。Symfony 7.4 LTSは2029年11月までセキュリティ修正を受けられます。Laravel 13はPHP 8.3〜8.5をサポートし、2028年3月までセキュリティ修正を受けられます。Laravel 12のセキュリティ修正は2027年2月に終わるため、新しく始めるプロジェクトなら13との互換性から評価するほうがよいでしょう。9

「まずPHPを上げる」が常に正しい順序とは限りません。古いフレームワークは最新のPHPでは動かない場合があるからです。そのため、現在のフレームワークの最新パッチ → サポートするPHPの範囲が広い中間のフレームワークバージョン → PHPのアップグレード → 目標のフレームワーク といった階段を踏む必要があるかもしれません。Composerが示す競合の原因と公式のアップグレードガイドでこの階段を設計します。中間バージョンをすべて本番にデプロイする必要はありません。

4. プロジェクトの種類別の経路

PHPレガシーは形態がさまざまなので、種類ごとに経路が異なります。以下の表は、主な種類ごとの基本的な経路と避けるべきアプローチです。

種類 基本的な経路 避けるべきアプローチ
手続き型PHP5とHTMLの混在 再現・バックアップ・特性テスト → DBアクセスと共通ロジックの分離 → PHPのアップグレード 最初の段階でLaravelとSPAへ全面変換
PHP7の自作MVC Composer・autoloadの導入と互換性分析 → ランタイムのアップグレード → モジュール境界の整理 ファイル移動・namespace変更・DB置き換えを同時に進める
古いLaravel・Symfony 公式のバージョン別ガイドに従って段階的にアップグレードし、Rectorとエージェントを併用 Composerのバージョン番号だけを最新に書き換える
CodeIgniter 2/3、FuelPHP、Zend1 維持・ラッピング・製品の置き換え・部分的な再構築を比較 互換性のないメジャーバージョン間での単純アップグレードを約束する
WordPressなどのCMS コアと拡張を標準の方法で更新し、自作のテーマ・プラグインだけを修正 エージェントでCMSのコアを直接修正する
Gnuboard・Youngcart・XE・Rhymix 正確な製品・fork・バージョンとコアの修正箇所から特定 コミュニティの「問題なく動く」という回答を互換性の保証として扱う
維持だけが必要な小さなサイト サポート対象の環境へ移行、使っていない機能を削除、静的化やSaaSでの代替を検討 業務上の価値を上回るプラットフォーム投資

CodeIgniter 3から4への移行について、公式ガイドは後方互換性のない書き直しであると明記しています。10 Drupal 7は2025年1月5日にコミュニティのセキュリティサポートが終了しており、現行のDrupalへの移行はコアファイルの置き換えではなく、データ・コンテンツタイプ・権限・URLの移管プロジェクトです。こうしたケースでは、PHPの構文変換だけで完了を判断してはいけません。

韓国国内の受託開発型サイトでは、優先して点検すべき項目が別にあります。EUC-KRとCP949のエンコーディング、添付ファイルのハングルのファイル名、Excelダウンロード、住所検索、本人認証・PG・SMS連携、Webエディターのアップロードパス、古いWebサーバーのrewriteルールです。Gnuboardのコアが最新のPHPで動作しても、古いskin・plugin・PGモジュールは動作しない可能性があります。

LaravelやSymfonyをすでに使っている顧客であれば、そのエコシステムを維持するほうが通常は有利です。他の言語は、特別な制約や運用上のメリットがある場合に、機能単位でのみ選択します。PHPから離れること自体を目標にはしません。

5. Rectorとエージェントの役割を分ける

第1部では、決定的なツールとエージェントの判断を分けるべきだと述べました。PHPでは、その決定的なツールの中心がRectorです。RectorはPHPコードをAST(構文木)レベルで解析し、決められたルールどおりに変換するツールです。バージョンアップグレード用のルールセットとフレームワーク別のルールを提供しており、ユーザー定義のルールも作成できます。11

FuelPHPからLaravelに移行した事例は、この方法の威力と限界をともに示しています。このチームは約80個のユーザー定義Rectorルールで、Query Builder、namespace、設定、request・responseの処理を変換し、PHPファイルの99%が自動変換されたと報告しました。しかし、ルートと認証の一部は手作業で処理しました。また、空のレスポンスのステータスコードやinsert結果の形式のような、フレームワーク間の小さな意味の違いが実際の問題になりました。作業は2021年1月から11月まで続き、QAとcanaryデプロイが重要でした。7 ファイルの99%の自動変換は、作業時間の99%削減や、検証なしでのデプロイを意味しません。

この事例はLLM以前のものですが、今日のエージェントと組み合わせると役割分担が明確になります。エージェントは繰り返しのパターンを見つけてRectorルールの草案とテストfixtureを作成し、ルールで処理できなかった残りの例外を解決します。確定したパターンは実行可能なRectorルールとして蓄積し、毎回LLMに新たに推論させることはしません。こうすることでコストが下がり、結果が再現可能になります。また、ルールベースの変換のdiffとエージェントによる修正のdiffを分けておけば、リグレッションが起きたときに原因を絞り込みやすくなります。

エージェントに与えるコンテキストも整備します。PrestaShopはPR 41279で、.aiディレクトリを共通ルールの原本とし、CLAUDE.md、AGENTS.md、Cursor・Copilot・Gemini・Windsurf用のエントリーファイルがすべて同じ情報を指すようにしました。12 このPRはドキュメントとツールの変更であり、製品移行の完了という成果ではありません。しかし、ルールの原本は1つにして、複数のエージェントのエントリーファイルがそれを指すようにする 構造は、そのまま取り入れる価値があります。Laravelプロジェクトであれば、route・schema・ドキュメントの情報をエージェントに提供するLaravel Boostを、PhpStormユーザーであれば、IDEの型・参照情報を外部のエージェントに提供するMCPサーバーを検討できます。13 ただし、Laravel BoostはLaravel 10以上かつPHP 8.1以上でしかインストールできないため、PHP5や古いLaravelのプロジェクトですぐに使えると想定してはいけません。

6. アップグレードで意味が変わるポイント

PHPのバージョンを上げると、コードが同じままでも動作が変わる箇所があります。エージェントはこうした箇所を構文エラーのように「直そう」とする傾向があるため、人が先に業務上の意味を決める必要があります。

リスク 例 確認方法
削除された機能 mysql_*、ereg、古いコンストラクタ構文 公式のバージョン別migration guide、PHPCompatibility
型の比較 数値文字列・null・false・空の値、0と文字列の比較 境界値にもとづく特性テスト
エラー処理 falseの戻り値を期待していたコードがTypeErrorや例外を受け取る 失敗経路とグローバルなerror handlerのチェック
動的プロパティ 宣言されていないオブジェクトのフィールド 実際のフィールドの契約を確認
文字列とエンコーディング EUC-KR・CP949・UTF-8、バイト長と文字長 DB・フォーム・ファイル・ダウンロードの往復テスト
DBドライバー 例外モード、fetchの型、影響を受けた行数 同じデータでクエリ結果とエラーを比較
シリアライズ セッション・キャッシュ・キューに保存されたPHPオブジェクト 新旧バージョン間の双方向の互換性を確認

たとえば、PHP 8では0 == "foo"の結果が以前のバージョンと異なります。14 これをすべての==を===に置き換えることで解決してはいけません。その比較がどのような入力を受け取り、どのような業務上の意味を持つのかを先に決める必要があります。同様に、PHP 8.2の動的プロパティの警告を#[AllowDynamicProperties]の一括適用で隠すのではなく、実際のフィールドとして宣言すべきものと、拡張ポイントとして残すものを区別します。PHP 7で削除されたmysql拡張をPDOやMySQLiに移行するときも、関数名だけを置き換えるのではなく、接続のcharset、SQLパラメータのバインディング、トランザクション、影響を受けた行数を検証します。

調査資料を検証することも必要です。Progritの事例の原文には、「Carbon 3がデフォルトでimmutableに変わった」というAI生成の分析が含まれていました。しかし、Carbonの公式ドキュメントは、Carbonは値が変更されるオブジェクトであり、CarbonImmutableは新しいオブジェクトを返すと区別しています。15 特定のアプリケーションがimmutableをデフォルトに設定することはできますが、それをライブラリ全体の変更と断定してはいけません。AIが作成した調査結果は、とくに実行の証拠で確認する必要があります。

7. DBと外部への副作用を統制する

PHPプロジェクトでは、DBが事実上の仕様であるケースが多くあります。そこで、データを扱う基本ルールを先に決めます。元のDBとアップロードファイルが復旧可能であることを検証してから変更を始めます。初期にはDBスキーマをできるかぎり維持し、まずアプリケーションの互換性を確保します。スキーマを変える必要がある場合は、カラムの追加 → データの投入 → 比較 → 利用側の切り替え → 旧カラムの削除という段階に分けます。そして、SQLiteベースの単体テストだけで本番のMySQLやMariaDBの動作を検証したとはみなしません。SQL mode、collation、ストレージエンジンの違いが、ソート・重複キー・日付処理の結果を変えるからです。

メール・SMS・PG・webhookの連携には、1つの原則があります。同じ入力を2回実行しても、実際の外部効果が2回起きないようにしなければなりません。 テスト用アダプター、サンドボックス、送信内容のキャプチャ、冪等性キー(idempotency key)を使います。新旧バージョンを並行実行するとき、新しい実装は送信の意図だけを記録するか、書き込みがブロックされた状態にしておきます。新旧の両方に同時に書き込むdual writeを基本的な解決策にはせず、DBに書き込む主体は1つに保ちます。cronとキューワーカーも、新旧の両方で同時に動かないようにします。

エージェントには本番DBの原本をそのまま渡しません。必要なスキーマと、非識別化・合成したデータを作って提供します。Laravel BoostやMCPサーバーのように、DBの参照やコマンド実行の能力を持つツールは、隔離された開発・検証環境にだけ接続します。ツールがそうした機能をサポートしているからといって、本番DBへのアクセス権限を広く与えることはしません。

8. Webのための検証ハーネス

第1部のC/C++の差分実行では、同じ入力に対する関数やライブラリの出力を比較しました。Webプロジェクトでは、出力の比較だけでは同等性を確認できません。HTTPリクエスト、ユーザー権限、セッション、DB変更前後の状態、ファイル、外部効果 を1つのシナリオにまとめて比較する必要があります。

比較実行は次の順序で進めます。まず、同じ基準データから、旧バージョンと新バージョンそれぞれに別々のDB・ファイル・セッション・キャッシュの領域を作ります。シナリオごとに各システムへ個別にログインし、CSRFトークンとCookieを発行してもらいます。同じ意味のリクエストを実行し、ステータスコード・redirect・レスポンス・DBの変更分・ファイル・メール送信の意図を収集します。タイムスタンプや自動採番IDのように比較から除外する値は、ドキュメントで定めたうえで正規化します。最後に、差分をオリジナルの欠陥・意図した変更・新たな欠陥・環境の違いに分類します。2つのアプリを同じDBに接続したまま書き込みシナリオを再生すると、オリジナルの実行結果が新バージョンの初期状態を汚染してしまうので、避けるべきです。

検証領域 最低限のシナリオ
非会員・会員・管理者 URL・APIの直接呼び出し、期限切れのセッション
ユーザー・テナントの分離 他のユーザーID・組織ID・ファイルIDでのリクエスト
入力・検索 空の値、0、null、多言語、最大長、SQLの特殊文字
書き込み 作成・更新・削除、二重送信、リトライ
外部効果 メール・SMS・PG・webhookの欠落・重複・誤った受信者
ファイル アップロード・ダウンロード、ハングルのファイル名、アクセス制御
日付・金額 月末、うるう年、タイムゾーン、端数処理
非同期・バッチ cron・キューのtimeoutと再実行

ユーザー・テナントの分離のシナリオは、2am.techの事例のIDORを捕捉するためのものです。Playwrightの画面スナップショット比較はレイアウトのリグレッションを見つけるのに役立ちますが、権限・精算・メール送信を証明するものではありません。16 また、「テストカバレッジ80%」のような数字を単独の成果指標にはしません。合格基準は、必須の業務シナリオがすべて通過、分類されていない差分が0件、未解決の権限・データの欠陥が0件、合意した性能予算を満たす、復元の訓練が完了、といった形で定めます。

エージェントに与える禁止事項には、公開事例の失敗をそのまま反映します。テストの期待値を緩めたり、skipで通過させたりしません。nullableへの変更、定数補正、権限の緩和で差分を隠しません。承認なしに認証・認可・DBスキーマを変えません。実際のメール・SMS・PGの副作用を起こしません。そして、実行できなかったチェックは「未実行」と報告させます。

9. 段階別の手順

PHPのモダナイゼーションでも、第1部と同じ方法で段階ごとに通過条件を設けます。違いは、G0で本番のコードとデータを確保する段階が別にあり、最後に継続管理の段階が加わる点です。

段階 実施作業 通過条件
G0 確保 Git、本番コード、DB、アップロードファイル、設定、バックアップの復元 復旧可能な基準バージョンとアクセス権限
G1 再現 旧バージョンと目標環境の構築、外部連携のstub 代表的なURL・CLI・cronを隔離環境で実行
G2 振る舞いの保護 特性・権限・DB・セッション・副作用のテスト オリジナルのベースラインとholdoutデータの確保
G3 経路の決定 バージョンの階段、パッケージの代替、画面・データ設計の決定 目標、禁止する変更、予算、ロールバックの合意
G4 自動変換 Rectorと専用ツールを小さなバッチで適用 ルールのfixture、diff、静的解析とテストの通過
G5 エージェントによる修正 残った型・設定・連携・業務上の例外を解決 検証基準を緩めていない修正と実行の証拠
G6 反証 新旧の比較、権限・データ・セキュリティ・性能の検証 原因が分類されていない差分と未解決のブロッキング欠陥がない
G7 切り替え デプロイartifact、canary、観測、復旧の訓練 セッション・データの互換性と副作用の統制
G8 継続管理 依存関係の更新とサポート終了への対応 担当者、スケジュール、定期検証の確定

G8はPHPでとくに重要です。PHPとフレームワークは毎年新しいバージョンが出て、2〜3年周期でサポートが終わります。一度最新バージョンに上げることよりも、アップグレード可能な状態を維持し続けること のほうが大きな価値を持ちます。匿名の顧客を対象としたBosunのパイロットは、この方向性を示しています。どの社内パッケージが外部パッケージの更新を妨げているかはAIを使わずに決定的な依存関係の探索で見つけ、バージョンの決定・公式ドキュメントの調査・修正・テスト・影響の説明はエージェントの段階としてまとめ、一度にメジャーバージョン1つずつ小さなPRを作成しました。17

セッションの扱いも、切り替え段階で注意すべき部分です。セッションを共有できない2つのバージョンの間でリクエストをランダムに振り分けると、ログアウト、CSRFの失敗、署名エラーが起きます。ユーザーグループごとに固定ルーティングにする、計画的に再ログインしてもらう、バージョンごとにセッション領域を分ける、といった方法のうち業務に合ったものを選びます。また、コードのロールバックとデータのロールバックは別物です。新しいコードが書いたデータを古いコードが処理できなければ、ルーティングを戻すだけでは復旧しません。

10. 最初の6週間のパイロットと止めるべきとき

以下は、アクセス権限と業務担当者がすでに確保された中規模のWebプロジェクトで、最初のパイロットを進める6週間の計画の例です。モダナイゼーション全体のスケジュールを保証する計画ではありません。

週 主な作業 判断の根拠
1 本番のベースライン、復旧、環境、依存関係の調査 実際のPHP・DB・デプロイ構成とブロッカー
2 代表的な業務・権限・DB・メールのテスト、バージョン経路の決定 オリジナルの動作と比較可能な契約
3 Rector、専用ツール、パッケージの更新 自動変換と残作業の分離
4 エージェントによる例外の修正と機能の接続 テスト・解析・差分実行とレビュー
5 holdout・セキュリティ・性能の検証、ロールバックの訓練 未分類の差分とブロッキング欠陥の解消
6 限定的な切り替え、観測、振り返り 実際の保守コストと拡大の可能性

最初のパイロットには、読み取り中心の管理画面や参照APIが適しています。しかし、DB・セッション・権限のリスクをすべて避けた単純なページを1つだけ選ぶと、代表性がありません。そのため、最初の小さな成功のあとは、代表的な書き込み機能を次の検証対象に含めます。決済・返金・個人情報の一括修正・権限管理・cron全体を、最初のパイロットで同時に変えることはしません。

エージェントの構成は、第1部と同じくシンプルに始めます。主力のエージェント1つに、IDEのPHPコード解析機能、Composer、Rector、静的解析(PHPStanまたはPsalm)、再現環境、CIを組み合わせます。並列化するときは、composer.lockと、共通の認証・ルート・スキーマの作成者を1人に決めます。複数のエージェントがそれぞれ依存関係の更新を実行すると、結果が互いに衝突します。

次のような状態が現れたら、自動化の規模を広げるのではなく、診断と契約を補強する必要があります。

  • 基準となるDBがなく、本番の状態を復旧できない。
  • 実際のDBではなく、ORMのモデルからスキーマを推測している。
  • テストを削除するか、nullable化・権限の緩和・定数補正をしないと通過しない。
  • エージェントが共通のセッション・認証・スキーマを同時に変えてしまい、原因を追跡できない。

反対に、サポートされていないベンダー製品や暗号化された拡張に中核機能が縛られているなら、コードのモダナイゼーションのかわりに、機能の廃止、SaaSへの移行、標準CMSの導入、小規模な再構築もあわせて比較します。古いコードを1行も変えないことも、すべて捨てることも目標ではありません。ビジネスに必要な機能を、許容できるコストとリスクで運用すること が目標です。

おわりに

コンサルティングでは、PHPのモダナイゼーションを一度の大きな契約で始めないようお勧めしています。最初の段階は診断です。本番のコード・実行環境・依存関係・ルート・権限・データ・デプロイを地図にまとめ、その結果をもとに、顧客が維持・アップグレード・部分的な置き換え・製品による代替・廃止のなかから選べるようにします。次が運用の安定化、その次がパイロットです。拡大の範囲は、パイロットで得た実際の証拠で決めます。リポジトリを見る前に、期間・自動化率・コスト削減率を約束することはしません。

このシリーズの2本の記事は異なる言語を扱いましたが、結論は同じです。エージェントがレガシーを前に迷うのは、コードが古いからではなく、変更が正しいかを確かめる手段がないからです。C/C++ではその確認手段がビルドの再現と差分実行であり、PHPでは本番環境の再現と、HTTP・権限・DB・外部効果を比較するハーネスです。検証環境が整えば、エージェントは本ブログで取り上げたソフトウェアファクトリーのように、調査・変換・反証を休みなく繰り返すことができます。18

本記事のスケジュール・しきい値・構成は、公開事例をもとにした提案です。実際に適用する前には、対象プロジェクトを診断して調整する必要があります。


  1. バイブコーディングでレガシーをモダナイズする(1/2):20年もののC/C++アプリケーション: https://roboco.io/ja/posts/legacy-cpp-vibe-coding-modernization/ ↩︎

  2. Thomas De Maesschalck, “How Codex Helped Me Modernize a 24 Year Old PHP Codebase” (2026-08-17): https://www.dvhardware.net/articles/31/dvhardware-codex-migration ↩︎

  3. Ryo Maruta, “生成AIを活用してPHP Laravelの大規模アップデートを実現した話” (2026-04-16): https://note.com/ryo_maruta/n/nb49789bf2ea8 ↩︎

  4. Rocket, “Accelerated modernization” (2025-11-07, 自社発表): https://careers.rocket.com/blog/technology-and-product/accelerated-modernization-rocket-mortgage-origination ↩︎

  5. Boldare, “AI in action: accelerating Symfony migration from 5.4 to 6.4” (2025-10-13): https://www.boldare.com/blog/ai-in-action-accelerating-symfony-migration-from-5.4-to-6.4/ ↩︎

  6. 2am.tech, “Using AI to Port a Legacy Laravel CMS to a Modern Architecture” (2026-10-08 更新): https://www.2am.tech/blog/using-ai-to-port-a-legacy-laravel-cms-to-a-modern-architecture ↩︎

  7. Rector, “Success Story of Automated Framework Migration from FuelPHP to Laravel of 400k+ Lines Application” (2022-02-07): https://getrector.com/blog/success-story-of-automated-framework-migration-from-fuelphp-to-laravel-of-400k-lines-application ↩︎ ↩︎

  8. PHP Supported Versions: https://www.php.net/supported-versions.php ↩︎

  9. Symfony Releases: https://symfony.com/releases , Laravel 13 Release Notes: https://laravel.com/docs/13.x/releases ↩︎

  10. CodeIgniter, Upgrading from 3.x to 4.x: https://codeigniter.com/user_guide/installation/upgrade_4xx.html ↩︎

  11. Rector Set Lists: https://getrector.com/documentation/set-lists ↩︎

  12. PrestaShop PR 41279 (2026-04-28 マージ): https://github.com/PrestaShop/PrestaShop/pull/41279 ↩︎

  13. Laravel AI Assisted Development: https://laravel.com/docs/13.x/ai , PhpStorm MCP Server: https://www.jetbrains.com/help/phpstorm/mcp-server.html ↩︎

  14. PHP 8 Backward Incompatible Changes: https://www.php.net/manual/en/migration80.incompatible.php ↩︎

  15. Carbon Introduction: https://carbon.nesbot.com/guide/getting-started/introduction.html ↩︎

  16. Playwright Visual comparisons: https://playwright.dev/docs/test-snapshots ↩︎

  17. Bosun, “Modernising a legacy PHP application”: https://bosun.ai/case-studies/case-study-legacy-dependency-platform-upgrades/ ↩︎

  18. バイブコーディングを超えて、ソフトウェアファクトリーへ: https://roboco.io/ja/posts/beyond-vibe-coding-software-factory/ ↩︎