はじめに
こんにちは、ヱビスです。
長く運用されているPHPシステムを改修するとき、依頼だけを見ると「項目を一つ追加するだけ」「表示を少し変えるだけ」に見えることがあります。
ところが、実際に調べ始めると、同じデータをバッチやCSV、帳票、外部APIでも使っていたり、コードには残っていない手作業の運用があったりします。小さく見えた変更が、思った以上に広い範囲へ影響することは珍しくありません。
私自身、既存システムの改修では、実装そのものよりも着手前の確認が大切だと感じていますし、この部分に時間をかけています。
コードだけを追って直し始めると、あとから環境差や例外データ、外部連携の存在が分かり、手戻りになることがあるためです。
そこで今回は、長期間運用されているPHPシステムを改修する前に、私ならどこを確認するかをまとめました。既存システムの調査を任されたときや、影響範囲に不安があるときのチェックリストとして使っていただければと思います。
最初に確認するのは「どう直すか」ではありません
改修の相談を受けると、つい実装方法から考えたくなります。ただ、その前に確認しておきたいことがあります。
- 現在はどのような動作になっているか
- 何が問題になっているか
- 改修後にどの状態になれば完了なのか
- 誰が、どの業務で使っているのか
- 影響を受ける画面、バッチ、帳票、外部連携はどこか
- 問題が起きたとき、どの状態まで戻せばよいか
特に気をつけたいのは、依頼された内容をそのまま仕様だと思わないことです。
たとえば「入力項目を追加してほしい」という相談でも、話を聞いてみると、本当に必要なのは検索条件やCSVの出力項目だったということがあります。先に実装へ入るのではなく、利用者が困っていることと、今回の完了条件を確認しておくと話がぶれにくくなります。
1. 現在の動作を残しておく
改修前の状態が分からなければ、変更後に正しく比較できません。
私はまず、対象機能を実際に動かして、次のような情報を残します。
- 対象画面の入力値と表示結果
- 正常系と、よく起きる異常系の動作
- 更新前後のデータ
- 出力されるCSV、PDF、メールの内容
- 関連するログ
- バッチの実行条件と実行結果
- 利用者が普段行っている操作手順
可能であれば、現在の挙動を固定するテストも先に追加します。
ここで作るテストは、「今の仕様が正しい」と保証するためのものではありません。今回の変更によって、既存の挙動が意図せず変わっていないかを確認するためのものです。こうしたテストは、一般に特性テスト(Characterization Test)と呼ばれます。
テストコードを追加するのが難しい場合でも、主要な操作を手動で確認できるスモークテストの手順は用意しておくと安心です。
2. 実際に動いている環境を確認する
リポジトリにあるコードだけでは、本番環境の状態までは分かりません。
まずはPHPのバージョンと設定を確認します。
php -v
php --ini
php -m
php -i | grep -E 'memory_limit|max_execution_time|date.timezone|default_charset|opcache'
ここで注意したいのが、CLIとWebサーバー側のPHPが同じとは限らないことです。
実際の現場では、次のような違いが残っていることがあります。
- CLIはPHP 8系でも、PHP-FPMはPHP 7系になっている
- CLIとPHP-FPMで別の
php.iniを読み込んでいる - Web経由で実行したときだけ有効になる拡張モジュールがある
- cronが、通常ログイン時とは異なるPATHで動いている
- 本番環境だけOPcacheが有効になっている
PHP以外の構成も確認します。
- OSとディストリビューション
- Apache、Nginx、PHP-FPMなどの構成
- データベースの種類とバージョン
- Redis、Memcachedなどのミドルウェア
- cron、キュー、常駐プロセス
- タイムゾーン、ロケール、文字コード
- ファイル保存先と権限
- Dockerなどのコンテナ構成
- 本番、検証、開発環境の差分
phpinfo() を使うと多くの情報を確認できますが、公開URLへ置いたままにすると機密情報が漏れる原因になります。利用する場合はアクセスを制限し、確認が終わったらすぐに削除します。
使用中のPHPがすでにサポートを終了している場合もあります。その場合、今回の改修と同時にすべてを解決しようとすると範囲が広がるため、まずは別のリスクとして記録し、アップグレード計画につなげます。
3. Composerと依存パッケージを確認する
Composerを使っているシステムでは、composer.json だけでなく composer.lock も確認します。
composer.json には利用できるバージョンの範囲が書かれています。一方、composer.lock には実際にインストールされるバージョンが固定されています。現在の状態を知りたいときは、両方を見る必要があります。
調査時には、たとえば次のコマンドを使います。
composer --version
composer validate --strict
composer show --direct
composer outdated --direct
composer audit
composer check-platform-reqs
確認するポイントはこのあたりです。
- フレームワークと主要ライブラリのバージョン
- 保守が終了している、または放棄されたパッケージ
- 既知の脆弱性
- PHPや拡張モジュールに求められるバージョン
- 独自パッケージやプライベートリポジトリ
- Composerプラグインとscriptsの内容
-
vendorディレクトリを直接修正していないか
調査を始めてすぐに composer update を実行するのは避けます。
composer update を実行すると、依存パッケージの解決結果が変わります。その時点で、調べたかった元の環境とは別の状態になってしまうかもしれません。まずは既存の composer.lock を基準に、現在の構成を把握します。
また、初めて扱うリポジトリで composer install を実行するときも注意が必要です。Composerのscriptsやプラグインからコードが実行されることがあるため、内容を確認したうえで、必要に応じて隔離した環境で次のように実行します。
composer install --no-plugins --no-scripts
もちろん、実際にアプリケーションを動かす段階では、scriptsやプラグインが必要になる場合があります。ここでの目的は、最初の調査で意図しないコードを実行しないことです。
4. システムへの入口を洗い出す
対象機能がWeb画面からしか呼ばれないとは限りません。
古いシステムほど、同じ処理がいろいろな経路から実行されていることがあります。
- Web画面とAPI
- 管理画面
- CLIコマンド
- cronから実行されるバッチ
- キューワーカー
- Webhook
- CSVやファイルの取込処理
- メール受信をきっかけに動く処理
- 他システムから直接呼ばれるエンドポイント
対象機能の処理経路は、簡単なものでよいので書き出しておきます。
画面・API
↓
ルーティング
↓
Controller
↓
Service / UseCase
↓
Repository / Model
↓
データベース・外部API・ファイル
同じ業務処理が、画面用、バッチ用、API用に別々に実装されているケースもあります。画面だけを直した結果、夜間バッチだけ古い処理のまま残る、といったことがないか確認します。
5. データベースは定義だけで判断しない
テーブル定義を読めば構造は分かりますが、実際のデータがどう使われているかまでは分かりません。
まずは次の点を確認します。
- DBMSとバージョン
- テーブル、インデックス、外部キー
- VIEW、TRIGGER、EVENT、ストアドプロシージャ
- 文字コードと照合順序
- SQLモード
- NULL、空文字、0の使い分け
- 論理削除の条件
- 重複データや孤立データ
- コード値、マスタ値、マジックナンバー
- 更新日時のタイムゾーン
- 採番方法
- トランザクションの範囲
MySQL系であれば、次のような情報を確認できます。
SELECT VERSION();
SELECT @@sql_mode;
SELECT @@character_set_server, @@collation_server;
SELECT @@system_time_zone, @@session.time_zone;
SHOW TRIGGERS;
SHOW EVENTS;
SHOW FULL TABLES WHERE Table_type = 'VIEW';
本番データには、コードから想像しにくい値が入っていることがあります。
たとえば、同じ項目にNULLと空文字が混在していたり、削除済みのデータを別の機能が参照していたりします。開発用のきれいなデータだけで判断せず、個人情報の扱いに配慮しながら、実データの傾向も確認します。
テーブル変更がある場合は、データ件数とロック時間も見ておきます。開発環境では一瞬で終わった ALTER TABLE が、本番環境では長時間のロックにつながることがあります。
バックアップについても、「取得している」と聞いただけでは安心できません。どの手順で復元するのか、実際に復元できるのかまで確認しておきます。
6. 外部システムとの連携を確認する
外部連携は、コードに書かれているURLを見つけただけでは十分ではありません。
少なくとも、次の内容を確認します。
- 接続先と用途
- 本番用・検証用エンドポイント
- 認証方式と秘密情報の保管場所
- APIキーや証明書の有効期限
- IPアドレス制限
- タイムアウトとリトライ
- レート制限
- 冪等性
- Webhook署名の検証
- 障害時の再送方法
- 相手側の担当者と連絡方法
リトライ処理がある場合は、同じ処理を複数回実行しても問題が起きないかを確認します。
決済、在庫更新、メール送信などは、単純に再実行すると二重処理になる可能性があります。正常時だけでなく、途中で通信が切れた場合や、相手側だけ処理が完了した場合も考えておきます。
7. コードの外にある業務ルールを確認する
長く使われているシステムでは、仕様がコードの中だけにあるとは限りません。
たとえば、次のような場所に分散しています。
- データベースのマスタ
-
.envや設定ファイル - 管理画面で変更できる設定
- cronの実行順序
- Excelやスプレッドシート
- 運用担当者の手作業
- 問い合わせ対応時の判断ルール
- 過去のIssue、チケット、メール
- Gitのコミット履歴とコメント
特に見落としやすいのが、運用担当者による手作業です。
「毎月CSVを出力したあと、担当者が列を修正している」「エラーになったデータだけ翌朝に再実行している」といった運用は、コードを読んでいるだけでは分かりません。
対象機能を使っている方には、普段の操作だけでなく、エラーが起きたときにどうしているかも聞いておくと、実際の仕様が見えやすくなります。
8. ログと監視を確認する
改修後に問題が起きたとしても、検知できなければ対応が遅れます。原因を追えるログがなければ、復旧にも時間がかかります。
確認しておきたいのは次の点です。
- PHPエラーログの出力先
- アプリケーションログの出力先
- ログレベル
- ローテーションと保存期間
- リクエストや処理を追跡できるID
- 個人情報や秘密情報が出力されていないか
- 例外監視サービスの有無
- 死活監視とアラート条件
- バッチ失敗の検知方法
- 業務上の異常を検知する指標
エラーが出ていなければ正常、とは限りません。
たとえば、受注処理自体は成功していても、普段100件あるはずのデータが5件しか作られていなければ、業務上は異常です。改修前に通常時の件数、処理時間、エラー率などを把握しておくと、リリース後の比較に使えます。
9. 認証・認可と機密情報を確認する
機能追加が繰り返されてきたシステムでは、権限チェックの方法が画面ごとに異なっていることがあります。
最低限、次の内容は確認します。
- ログインしていない利用者からアクセスできないか
- 一般利用者と管理者の権限が正しく分かれているか
- URLやIDを書き換えて、他人のデータへアクセスできないか
- CSRF対策が入っているか
- SQLインジェクション対策ができているか
- XSS対策ができているか
- ファイルアップロードに制限があるか
- セッションとCookieの設定に問題がないか
- パスワードやAPIキーが適切な場所に保管されているか
- ログや例外画面へ機密情報が出ていないか
- リポジトリに秘密情報が含まれていないか
依存パッケージについては、Composerの監査機能などを使って既知の脆弱性も確認します。
ただし、改修のたびにシステム全体のセキュリティ問題をすべて直そうとすると、今回の目的がぼやけます。今回の変更を安全に行うために必要な対応と、別途計画する改善は分けて記録します。
10. テストとリリース方法を確認する
テストコードが置かれていても、現在も問題なく実行できるとは限りません。
まずは、以下を確認します。
- テストの実行コマンド
- 必要なPHP拡張とミドルウェア
- テスト用データベースの作成方法
- CIで実行されているテスト
- 実行されていない、または常に失敗しているテスト
- カバレッジの対象外になっている領域
- 手動確認が必要な機能
続けて、リリース方法も確認します。
- 誰が、どの手順でデプロイするか
- Git pull、rsync、成果物配置など、どの方式を使っているか
-
composer installをどこで実行するか - DBマイグレーションの実行順序
- メンテナンスモードの有無
- キャッシュとOPcacheの削除方法
- キューワーカーの再起動方法
- cronとの競合
- リリース後に確認する項目
- 問題が起きた場合の切り戻し手順
DB変更を含む場合は、コードだけを以前のバージョンへ戻しても動かないことがあります。
ロールバックを考えるときは、コード、データベース、設定、キャッシュ、外部システムへの送信結果をどこまで戻せるのかを整理します。元に戻せないデータ更新がある場合は、単純な切り戻しではなく、前方修正で対応する手順も必要です。
変更内容ごとの主なリスク
| 変更内容 | 主なリスク | 先に確認すること |
|---|---|---|
| 画面表示だけの変更 | 他画面との共通テンプレート、XSS、表示崩れ | 共通部品、対象ブラウザ、エスケープ処理 |
| 業務ロジックの変更 | バッチやAPIとの処理差分 | 同じロジックを使う入口、例外時の運用 |
| DB項目・テーブルの追加 | ロック、既存SQL、CSVや帳票への影響 | 件数、インデックス、NULL、移行手順 |
| Composer依存関係の変更 | 間接依存の更新、非互換、脆弱性 |
composer.lock、変更差分、移行ガイド |
| PHPバージョンの変更 | 非互換、非推奨機能、拡張差分 | PHP公式の移行ガイド、テスト、実行環境差 |
| 外部APIの変更 | 二重処理、認証、レート制限 | リトライ、冪等性、検証環境、障害時対応 |
改修前に避けたいこと
いきなり大規模なリファクタリングを始める
目的の改修と構造改善を同時に進めると、問題が起きたときに原因を切り分けにくくなります。
今回の変更を安全に行うために必要な整理と、将来的に行いたい改善は分けて考えます。
調査中に依存パッケージを更新する
調査中にバージョンを更新すると、元からあった問題と、更新によって発生した問題を区別しにくくなります。
依存関係の更新は、目的と検証範囲を決めた別の変更として扱います。
開発環境で動いたことだけを根拠にする
本番環境では、PHPやDBのバージョン、設定、データ量、権限、ネットワークなどが異なることがあります。
開発環境で動いたことを確認したうえで、本番との差分も確認します。
テーブル定義を正しい仕様だと思い込む
長く運用されているシステムでは、実データや手作業の運用が、事実上の仕様になっていることがあります。
スキーマだけで判断せず、コード、データ、運用も合わせて確認します。
改修前チェックリスト
要件と現状
- 現在の動作を再現できる
- 改修の目的と完了条件が明確になっている
- 利用者と業務上の影響を確認した
- 正常系と代表的な異常系を記録した
実行環境
- CLIとWeb側のPHPバージョンを確認した
-
読み込まれる
php.iniと拡張モジュールを確認した - OS、Webサーバー、DB、ミドルウェアを確認した
- 本番、検証、開発環境の差分を確認した
- PHPのサポート状況を確認した
依存関係
-
composer.jsonとcomposer.lockを確認した - 主要パッケージと独自パッケージを確認した
- 既知の脆弱性と放棄されたパッケージを確認した
- Composerのscriptsとプラグインを確認した
-
調査段階で不用意に
composer updateを実行していない
データと連携
- DBのバージョン、文字コード、SQLモードを確認した
- VIEW、TRIGGER、EVENTなどを確認した
- 実データのNULL、重複、例外値を確認した
- 外部API、Webhook、ファイル連携を確認した
- バックアップと復元方法を確認した
テストとリリース
- 自動テスト、または手動スモークテストを用意した
- ログと監視で問題を検知できる
- デプロイ手順を確認した
- DB変更を含むリリース順序を確認した
- ロールバック、または前方修正の手順を用意した
改修前に用意しておきたい3つの成果物
すべてを詳しい設計書にする必要はありません。最低限、次の3つを残しておくだけでも改修を進めやすくなります。
-
現行環境一覧
PHP、フレームワーク、DB、ミドルウェア、主要パッケージ、cron、外部連携をまとめたものです。 -
影響範囲メモ
対象画面、処理経路、参照・更新テーブル、関連バッチ、帳票、APIをまとめたものです。 -
確認・切り戻し手順
リリース前後の確認項目、異常と判断する基準、問題が起きた場合の対応をまとめたものです。
まとめ
長期間運用されているPHPシステムを改修するとき、最初に作るものはコードではありません。
まず必要なのは、現在のシステムがどの環境で動き、どのデータや外部システムに依存し、誰がどのように使っているのかを把握することです。そのうえで、変更後に何を確認するのか、問題が起きたときにどう戻すのかを決めます。
既存システムでは、コードをきれいにすること以上に、今の業務を止めずに変更できることが大切です。
改修前の調査には時間がかかります。ただ、その時間を惜しまないことで、実装後の手戻りや障害対応、原因調査にかかる時間を減らせます。
これから既存PHPシステムの改修へ入る方にとって、今回の内容が確認漏れを減らすきっかけになれば幸いです。