こんにちは!ロリポップ・ムームードメイン事業部ムームードメイングループの中村(@litencatt)です。普段はエンジニアリングリードとしてムームードメイングループのサービスの開発・運用をメインで担当しています。
今回は、PHP 4.4製のレガシーな社内業務用Webアプリケーションに対して、AIエージェント(Claude Code)を活用しながらDocker開発環境の構築とCI上でのユニットテスト自動実行を実現した話を紹介します。
レガシーコードへのCI整備は「大変そう」という印象で後回しにされがちですが、AIエージェントをどう働かせるかを意識することで、従来よりも大幅に効率よく進められました。その具体的な進め方も合わせてお伝えします。
- 背景:テストはあるのに、CIで実行されていなかった
- AIエージェントの働かせ方:基本方針
- Step 1: スタンドアロンDocker環境の構築
- Step 2: CIでテストを動かす
- 詰まったポイントと解決策
- AIエージェントとの協業パターンまとめ
- まとめ
背景:テストはあるのに、CIで実行されていなかった
対象リポジトリにはSimpleTestを使ったユニットテストが97ファイル・480メソッド存在していました。SimpleTest自体も当時は広く使われていたテストフレームワークですが、現在のPHP界隈ではPHPUnitに置き換えられて久しいレガシーなフレームワークです。CIのパイプラインにはlintとセキュリティチェックのみが含まれており、これらのユニットテストはCI上で一度も実行されていない状態でした。
アプリケーション自体はKubernetes上で稼働しており、本番用のDockerfileは存在しています。しかし、以下の理由からCIでテストを実行できる環境が整っていませんでした。
- 関連サービスのリポジトリに依存した開発環境しかなく、対象アプリケーション単体でテストを走らせる環境が整備されていなかった
- PHP 4.4という古い環境を現代のCI(GitHub Actions)で動かすには多くの課題があった
- 特に、GitHub ActionsのランナーはUbuntu(Debian系)ベースであるため、本番環境(CentOS 6)向けの既存Dockerfileをそのまま転用できず、glibc等のバージョン制約に対応したCI専用Dockerfileを新たに作成する必要があった
この状態を解消するために、以下の2ステップで対応しました。
- Step 1: スタンドアロンDocker開発環境を追加
- Step 2: CIにユニットテストを追加し、テスト実行環境を整備
AIエージェントの働かせ方:基本方針
作業に入る前に、今回意識したAIエージェントの使い方の方針をまとめます。
「AIが得意なこと」に仕事を割り振る
レガシーコード対応においてAIが特に力を発揮するのは、次の3つの場面です。
① 広い調査・横断的な修正 コードベース全体を把握した上で「このパターンが何箇所あるか」「全部直すとどうなるか」を一気に処理できます。人間が数十ファイルを手作業で修正するより圧倒的に速く、抜け漏れも少ないです。
② 古い技術のリサーチ PHP 4.4やMySQL 3.x時代のAPIの挙動など、検索してもほとんど情報が出てこない領域でも、学習データに基づく知識から解決策の候補を提示してくれます。ただし、こうしたニッチな領域では回答が不正確な場合もあるため、提案された解決策は必ず実際に検証してから採用することが重要です。
③ 並列仮説検証 原因が特定できない問題に対して、複数の仮説を同時にコード化してCIで検証する、という進め方をAIと組み合わせることで、試行錯誤のサイクルを大幅に短縮できます。
④ マルチエージェントによる並列作業 oh-my-claudecode などのプラグインを活用すると、複数のAIエージェントを並列で動かすことができます。たとえば調査・実装・レビューをそれぞれ別のエージェントに同時に担当させることで、シングルエージェントでは直列になりがちな作業を並列化できます。今回は主にシングルエージェントで進めましたが、より複雑なタスクではこうしたオーケストレーションの仕組みをあわせて活用するとさらに効率が上がります。
人間は「判断」と「レビュー」に集中する
AIが提案した変更が「安全か」「意図を変えていないか」を判断するのは常に人間の役割です。特にレガシーコードは動作の根拠がコード外に存在することも多いため、AIの出力を盲目的に適用せず、必ずレビューして承認する体制を維持しました。
Step 1: スタンドアロンDocker環境の構築
まず、関連サービスのリポジトリに依存せずアプリケーション単体で起動・テストできるDocker環境を作りました。
# compose.yml (抜粋)
services:
mysql:
image: mysql:5.6
# ujis (EUC-JP) 文字セット対応
app:
image: <アプリケーションイメージ>
test:
image: <CI用PHP4イメージ>
Makefileには docker/setup, docker/start, docker/stop, docker/test などのターゲットを追加し、開発者が手順を覚えずとも環境を操作できるようにしました。
DB初期化スクリプト(CREATE TABLE、seed、権限付与)もこのステップで整備し、make docker/setup 一発でDBを含む全環境が立ち上がるようにしています。
AIの働かせ方:既存コードを渡してスキャフォールドを生成
このステップでは、k8s上の本番環境向けに存在していたDockerfileや関連リポジトリのcompose設定を渡し、「単体で動くローカル開発環境を作って」と依頼する形で進めました。
ゼロから書くより、既存の構成を参照させることで、文字セットやポート設定などのアプリケーション固有の制約を引き継いだ設定を生成してくれます。コミット履歴の大半に Co-Authored-By: Claude が残っているのは、このように対話的に実装を進めた結果です。もちろん、各コミットはマージ前に人間がレビュー・動作確認しています。
Step 2: CIでテストを動かす
Docker環境でローカルのテストが通るようになったら、次はCI(GitHub Actions)でも同じテストを走らせます。しかしここから、PHP 4.4という古い環境をモダンなCIで動かすための数々の課題が待ち受けていました。
結果
作業期間: Step 1のDocker環境構築に約2日(2/4〜2/6)、Step 2のCI整備に約5日(2/6〜2/10)、合計約1週間。特に難易度が高かったCIデバッグは、2/6の1日(約13時間)で主要な問題をほぼ解決できました。
最終的に、CIで 480テストメソッド、6463アサーション、0 failures, 0 exceptions を達成しました。
※ アサーション数がテストメソッド数に対して多く見えるのは、マスターテーブルの全レコードに対して各カラムの値を1件ずつチェックするようなテストが含まれているためです。テストの網羅性そのものを示す数字ではありません。
| ジョブ | 結果 | 所要時間 | 内容 |
|---|---|---|---|
| test | ✅ pass | 6m48s | SimpleTestによるユニットテスト(MySQL 5.6サービスコンテナ付き) |
| lint | ✅ pass | 2m14s | php -l によるPHP構文チェック |
| Security check | ✅ pass | 1m14s | php-security-check による既知の脆弱性のあるパッケージの検出(reviewdogでPRにコメント) |
以下では、ハマったポイントとその解決策、そして各場面でのAIの働かせ方を紹介します。
詰まったポイントと解決策
(1) mysql_real_escape_string() による謎の12~16分ハング
CIでテストを走らせると、init.phpの読み込みだけで12~16分かかる謎の現象が発生しました。
AIの働かせ方:並列仮説検証ワークフローを生成させる
原因が特定できない状況で、Claudeに「複数の仮説を同時に検証できるCIワークフローを作って」と依頼しました。
Claudeはまず仮説を列挙し、それぞれに対応した条件を変えたジョブを一つのワークフローファイルにまとめて生成しました。GitHub Actionsでは同一ワークフロー内の独立したジョブは並列実行されるため、11の仮説を一度のCI実行で同時に検証できます。各ジョブにはinit.phpの各requireの前後にタイムスタンプ付きのtraceログも埋め込まれており、どの処理でハングしているかを一目で把握できる形になっていました。
# 調査用ワークフロー (ci-debug.yml)
jobs:
diagnostics: # PHP設定診断
baseline: # 現状CI設定
obflush: # ob_flush強制フラッシュ
implicit-flush: # implicit_flush=1
no-init: # init.phpなし(DB接続を切り離して検証)
# ... 計11ジョブ(全て並列実行)
「仮説の列挙→ジョブのコード化→並列実行→結果の比較」という調査サイクルを、人間がひとつひとつ試すことなくAIとCIに任せることができました。自分で1件ずつ試していたら何日もかかるような調査を、数時間で原因まで絞り込めました。
判明した原因は2つでした。
原因①: mysql_real_escape_string() に接続パラメータを渡さないと、有効な接続がない場合に /tmp/mysql.sock への自動接続を試行し、OSレベルのソケットタイムアウト(約4分)が発生していた。
// Before: 暗黙の自動接続でタイムアウト
mysql_real_escape_string($str);
// After: 明示的に接続を渡す
mysql_real_escape_string($str, $conn);
原因②: DB接続のリトライ処理で mysql_connect() が毎回60秒タイムアウトし、3回リトライで合計最大180秒かかっていた。mysql.connect_timeout = 3 を設定することで全体を約18秒に短縮。
なお、この
$connを明示的に渡す修正はパフォーマンス改善だけでなく、複数のDB接続で異なる文字セットが使われている場合のSQLインジェクションリスクの軽減にもつながります(マルチバイト文字セット環境下でのエスケープ処理の文字セット誤認対策)。
(2) PHP 4バンドルのMySQLクライアントとMySQL 5.6の非互換
PHP 4.4にバンドルされている古いMySQLクライアントライブラリ(約3.23時代)が、MySQL 5.6の新しい認証プロトコルに対応しておらず、Lost connection during query エラーが発生しました。
AIの働かせ方:古い技術の知識を活用させる
このような「PHP 4のMySQLクライアントとMySQL 5.6の互換性問題」は、現在のWeb上にはほとんど情報がありません。Claudeに症状を伝えると、MariaDBクライアントライブラリへのシンボリックリンクを使って回避する方法を提案してくれました。自分ではたどり着けなかった解法です。
# MariaDBクライアントライブラリのシンボリックリンクを作成
RUN ln -s /usr/lib/x86_64-linux-gnu/libmariadb.so.3 /usr/lib/libmysqlclient.so.15
--with-mysql=/usr でシステムライブラリを使用するよう変更することで解決しました。
(3) DotReporterのドットがCIで表示されない
PHPUnit風のDotReporterを実装してCIのログを見やすくしようとしたところ、ドットが一切表示されない問題が発生しました。
原因はSAPI(Server Application Programming Interface:PHPの実行モード)の違いでした。CGI SAPIはWebサーバー経由のリクエスト処理向けで、flush() が内部バッファをフラッシュできず、全テスト終了後に一括出力されていました。--disable-cgi を追加してCLI SAPI(コマンドライン実行モード)でPHPをビルドすることで解決しました。
# Before
RUN ./configure ...
# After: CLI SAPIでビルド
RUN ./configure --disable-cgi ...
(4) CI専用Dockerfileの作成とPHP拡張のビルド失敗
GitHub ActionsのランナーはDebian/Ubuntu系のglibcバージョンを前提とするため、本番環境(CentOS 6)向けのDockerfileをそのままCI用イメージのベースとして使うことができませんでした。そのためCI専用のDockerfileをDebian 10ベースで新たに作成することにしましたが、今度は本番環境に存在していたPHP拡張が同じ方法でビルドできないケースがいくつか発生しました。
| 拡張 | 問題 | 解決策 |
|---|---|---|
| mhash | Debian 10にmhash-develがなかった | libmhash-devを追加 |
| curl | ヘッダーがマルチアーキテクチャ対応パス(/usr/include/x86_64-linux-gnu/curl/)にあり見つからない |
シンボリックリンクを作成 |
| json | PHP 4.4にはjsonが組み込まれていない | PECL(PHP Extension Community Library:PHP拡張のリポジトリ)からjson-1.2.1をビルド |
| idnkit | ソースビルドが必要 | ソースからビルド |
AIの働かせ方:エラーメッセージをそのまま渡して解決策を生成させる
各拡張のビルドエラーは、エラーメッセージをそのままClaudeに渡して「Dockerfileに修正を加えて」と依頼する形で解決しました。原因の説明と修正案を一緒に提示してくれるため、理解しながら進められます。
(5) Cannot redeclare エラー(PHP 4.4の名前空間問題)
SimpleTestは全テストファイルを一括でrequireするため、複数ファイルで同名の関数が定義されていると Cannot redeclare エラーが発生します。PHP 4.4には名前空間がなく、全関数がグローバル空間に展開されます。
// function_exists ガードを追加
if (!function_exists('someFunction')) {
function someFunction($param) { ... }
}
// または、ファイル固有のサフィックスを付けてリネーム
function someFunction_FileSpecificSuffix() { ... }
AIの働かせ方:コードベース横断の修正を一括で任せる
「ガードを入れるべき関数を全て特定して、適切な方法で修正して」と依頼すると、数十ファイルにまたがる重複を検出し、関数の役割に応じてガード追加かリネームかを判断しながら修正してくれました。人間が手作業で行うと見落としが出やすい作業です。
(6) Flaky Test の修正
テスト間でDBのデータが残ってしまい、実行順序によってテストが失敗するケースがありました。
teardown()を追加してテストデータをクリーンアップ- テストデータ生成の乱数範囲を拡大(
mt_rand(0,99)→mt_rand(0,999999))して衝突確率を大幅に低減 - 各テストケースに固有プレフィックスを使用
(7) AllTestsRoot.php のCWD依存
テストファイルのrequireパスがカレントディレクトリ依存だったため、CIの実行環境でファイルが見つからない問題が発生しました。dirname(__FILE__) ベースのパスに統一することで解決しました。
AIエージェントとの協業パターンまとめ
今回の作業を通じて、レガシーコードのCI整備においてAIが特に有効だった場面を整理します。
| パターン | 内容 | 今回の例 |
|---|---|---|
| 並列仮説検証 | 原因が特定できない問題に対して複数の仮説をCIジョブとして一気に試す | 12~16分ハングの原因特定 |
| 古い技術の知識 | Web上に情報が少ない古い技術の問題を内部知識で解決 | PHP4+MySQL5.6の非互換、マルチアーキテクチャ対応 |
| 横断的な修正 | コードベース全体にまたがる同一パターンの検出・修正 | Cannot redeclare エラーの一括での修正 |
| スキャフォールド生成 | 既存コードを参照させてボイラープレートを自動生成 | Docker環境・CI設定の初期作成 |
| エラー起点の調査 | エラーメッセージを渡して原因と解決策を提示させる | PHP拡張ビルドエラーの解消 |
これらに共通するのは、「調査・生成・修正はAIに任せ、判断・承認は人間が行う」という分担です。特にレガシーコードは副作用の把握が難しいため、AIの提案を受け入れる前に必ず意図を確認することを習慣にしました。
まとめ
PHP 4.4という現代のCI環境とは大きくかけ離れたレガシーな環境でも、AIエージェントをうまく働かせることで現実的な時間でCI整備を達成できました。
今回の取り組みにより、
- 480テストメソッドがCIで自動実行されるようになり、masterへのマージ前にテスト通過が必須となりました
- テスト実行時間は約7分と開発サイクルを阻害しない速度を実現できました
「AIは新しいコードを書くためのもの」というイメージを持たれがちですが、レガシーコードの課題解決においても十分な力を発揮します。特に「横断的な調査」「古い技術の知識」「並列仮説検証」の3つは、人間だけで取り組むと時間がかかる作業をAIが大幅に加速してくれる領域です。
なお、PHP 4.4は2008年にEOLを迎えており、今回整備したCI環境はあくまでリグレッション検出の安全網を作ることが目的です。このシステム自体は別の社内業務システムへの移行計画を進めています。
同じような課題を抱えているチームの参考になれば幸いです。