CakePHPのControllerがディレクトリの階層に対応していた件

(追記)本記事の内容はCakePHP1.3.6ならびに2011/04/28時点でのgituhbの1.2, 1.3ブランチで検証しました。

(2011/05/05追記)コメントで現時点のCake2.0では再帰的な探索を行わないとのご指摘を頂きました。ちゃんと追えてないけど、import周りの処理は大幅に変更されるようです。

CakePHPではControllerは1つのディレクトリに全部入れるのがふつう」という話を聞いて、さすがにそれはないだろうと思って調べたらapp/config/bootstrap.phpに$controllerPathsを書けば探索パスを追加できるという情報を得た。

のだけど、どうにもうまく動かなくてあちこちいじくり回してたらいつの間にか動くようになったり、別のサーバにdeployしたらまた動かなくなったりして挙動が怪しい。

本体のコードを見ると、$controllerPathsなんてどこにも書いてなくて何かが間違っているように見える。

によれば1.3になった時点で$controllerPathsは使えなくなったらしい(それならそれでwarningなりと吐けよと思うのだが)。

で、$controllerPathsが動作することを期待したコードは動かないはずなのだが、目の前には何故か動く環境と動かない環境があって意味がわからないよ。と思ってコードを追ってみるといろいろわかった。

■App::importの処理

App::importはファイルを探索する際の対象パスとして、file_mapキャッシュ(app/tmp/cache/persistent/cake_core_file_map)を参照する。このキャッシュは1行目が有効期限、2行目がシリアライズしたデータになっており、以下のコードを実行することで内容を確認できる。

php -r '$c=explode("\n",file_get_contents("app/tmp/cache/persistent/cake_core_file_map"));var_dump(unserialize($c[1]));

以下はApp::importがControllerを読み込む時のあるユースケースにおけるフローを読んだメモ。

  • file_mapに入ってるなら読み込みを試みる
    • 成功したらそこでreturn
    • 失敗したらfile_mapキャッシュの該当キー削除を予約する
  • file_mapに無い場合はあれこれ通って、App::__findがファイルを探す
    • controllerの探索パスをdir_mapキャッシュ(app/tmp/cache/persistent/cake_core_dir_map)から取得
    • dir_mapにキーが無ければディレクトリを「再帰的に」たどって探索パスのリストを作る($this->__pathsに入れる)
    • 作った探索パスのリストから、Controllerの読み込みを試みる
      • 読み込みに成功したらキャッシュの更新を予約する

ざっくりとした流れは「file_map探す → dir_mapをキャッシュから取る、無ければ作る → dir_mapからファイル探索」といったところ。

ここで問題になるのが、dir_mapが正しいことを誰も保証してくれないこと。dir_mapにキーがなければディレクトリを読んでくれるが、古いデータがdir_mapに入っていた時にそれを更新する仕組みがない。

■経緯の妄想

ここから先は調べるのが面倒なので、憶測だけ書き散らかす。誰か詳しく知っていたら教えてください。

  • ある時点まではApp::__findはディレクトリを再帰的にたどる機能を持っていなかった
  • なので、dir_mapが更新されるのはアプリケーションの設計が大きく変わる時だけだった
  • 「それぐらい変わる場合はキャッシュを手動で消せよ」で済んでいた
  • ある時からApp::__findがrecursiveをサポートするようになった
  • dir_mapが更新されるべきケースが出てきた
  • 面倒なので、あるいはそういうケースは特殊なので放置している

App::__findでControllerも再帰的に探索できるという情報が広まっていないようなので、この機能はCake的には位置付けとして重要でないのかも知れない。

そもそも、この機能はドキュメントに載っていないんじゃないかと思ったが、あらためて探してみると、公式ドキュメントの「2.4.1 ファイルとクラス名の規約」に

各ファイルは、 app フォルダ内のそれぞれ適切なフォルダの中かその下(サブフォルダも可)に設置します。

とオマケ程度に触れられているのが確認できた。


このdir_mapが更新されない問題は、1ヶ月ぐらい前の2.0のブランチでも変わっていなかったので、このあたりは「お前がキャッシュ消せば済む話」という扱いなのかも知れない。最新版ではコードの構成が根本的に変わっていたので追いきれていない。

■まとめ

  • Controllerをサブディレクトリに配置する機能は標準でサポートしている
  • ディレクトリ構造を変えた時は、dir_mapのキャッシュを消す必要がある