PHPの巨大なレガシーWebサービスを扱っていると、一体どれが最初にアクセスされるPHPファイルなのかよく分からず、色々と困ったことにぶつかります。
そういった問題を解決するにあたり、僕が使った方法をまとめてみます。
アプリケーションの初期化を一元化したい、フレームワークを入れるために処理の流れを根本的に見直したい方々の助けになれれば幸いです。
エントリポイントが分からない時の問題点
まず最初に、この問題によって困るポイントをまとめてみます。
以下の項目が思いつきました。
- アプリケーションの初期化処理をどこに入れればいいのか分からない。
- エントリポイントごとに初期化処理がバラバラ。
- ビジネスロジック部分でどの初期化処理がされているのかを期待していいのか分からない。
- 自動テスト実行時にどの初期化処理がされているべきか定義できない。
- 公開/非公開ファイルの線引きが出来ない。
- エントリポイントのみを公開ファイルにしたい。
- フレームワークのようにアプリケーション内でルーティングするようにしたくても、ルーティング先のファイル(現エントリポイント)が分からなくて出来ない。
ビジネスロジック部分でどの初期化処理がされているのを期待していいのか分からない、というのが特に開発効率に影響を与えていると僕は思っています。
僕の関わっているWebサービスではどこでも利用できるとされているシングルトンオブジェクトがいくつかある(あまり良くないのですが・・・)のですが、その中には初期化が必要なものもいくつかあります。
で、そいつを「どこでも利用できるはず」と信じて共通ライブラリ的なクラスで使ってみると、エントリポイントによっては初期化されておらず、見事にエラーになったりしてサービスに影響が出たりします。
一般的なWebフレームワークは1個の公開ファイル内で初期化処理を行っているのでこんなことはないのですが、エントリポイントがバラバラだとそれが出来ないので、常にどこからアクセスされたのか?を意識しなくてはなりません。
これがまた、本当につらい・・・。
エントリポイントを特定する方法を考える
上記問題を解決するためにエントリポイントを特定を始めたのですが、ひたすらソースやアクセスログを追うのはさすがに無理なので、もっと手っ取り早い方法がないか探していました。
そこで見つけたのが $_SERVER['SCRIPT_NAME']
で、公式サイトによれば現在のスクリプトのパスがこの変数に入っています。
現在のスクリプトのパス、というのはカレントのファイルパス(=__FILE__
)という意味ではなく、 最初に実行されたPHPファイル が入っています。
$_SERVER
ってApacheやNginxなどのWebサーバから提供される情報だと思っていたのですが、$_SERVER['SCRIPT_NAME']
はクーロンやCLIからの実行時でもきちんと値が入っていて、常に取得できました。
こいつをログ収集し続ければ、いずれ全てのエントリポイントを特定することが出来ると考えました。
$_SERVER['SCRIPT_NAME']をログ収集する
ログ収集するにもどうやってログに吐き出すか?という問題があるのですが、訳あってPHPプロセス終了時にログ出力したい理由がありました。
その理由は今回の本題とは全く関係ないので、ここでは触れません。
最終的にログ出力方法はphp.iniのauto_prepend_fileとregister_shutdown_functionメソッドの合わせ技を採用しました。
auto_prepend_fileはPHPプロセス開始時に自動的にrequireするファイルを指定するディレクティブで、必ずログ出力処理が呼び出される保証が得られます。
register_shutdown_function
メソッドはPHPプロセス終了時に自動的に呼び出すfunctionを登録するメソッドです。
ここに$_SERVER['SCRIPT_NAME']
をログ出力するfunctionを登録しました。
「PHP実行後にログ出力したいならauto_append_fileで良くね?」って話もあったんですが、こっちはexit
メソッドで終了する場合には呼び出されないので採用しませんでした。register_shutdown_function
メソッドはexit
メソッドで終了する場合も呼び出されます。
そんなこんなでエントリポイントが特定できるようになった
上記の対応をして無事にエントリポイントを全て特定することが出来ました。
ただそもそもの話、最初の問題点で挙がっていた共通の初期化処理したいなら、php.iniのauto_prepend_file使うだけでいいんじゃない?
という考え方もあるのですが、それだとphp.iniを見ないと挙動の分からないアプリケーションとなってしまいます。
アプリの挙動はリポジトリを見るだけで把握できるのが理想だと思うので、特殊な理由が無い限りはなるべく使いたくはありません。
今回はログ収集で使ってしまいましたが、エントリポイント特定完了後はちゃんと削除しました。
また将来的にフレームワークを導入したいので、エントリポイントを一元化する必要が元々ありました。
そのためにもauto_prepend_fileによる解決を今回はしませんでした。