CakePHP2で各コントローラーにApp::uses(‘AppController’, ‘Controller’)が必要な理由

タイトルの件。
サンプルのコントローラーを見ると、App::usesを利用してAppControllerを宣言しているけど、実際これはない場合にも動作しているのでイマイチ必要な理由がわからなかった。
でも今回プラグインを作った時にやっとわかったという話。

とりあえず現象を確認するために以下の順で試してみる。

1. A,B,Cというプラグインを作る。

Plugin
∟A
 ∟Controller
 ∟Model
 ∟View
∟B
 ∟Controller
 ∟Model
 ∟View
∟C
 ∟Controller
 ∟Model
 ∟View

2. それぞれにAppControllerを継承した以下のようなクラスを作成する

App::uses ('AppController' , 'Controller' );
class AAppController extends AppController {
}
App::uses ('AppController' , 'Controller' );
class BAppController extends AppController {
}
App::uses ('AppController' , 'Controller' );
class CAppController extends AppController {
}

3. それぞれに振り分けのデフォルト(ここではIndexControllerのIndexアクションとする)を用意する

※ここでは継承元のクラスをApp::usesしない

class IndexController extends AAppController {
    public $name = 'Index';
    public function index(){}
}
class IndexController extends AAppController {
    public $name = 'Index';
    public function index(){}
}
class IndexController extends AAppController {
    public $name = 'Index';
    public function index(){}
}

4.各プラグインにルーティングするようにroutes.phpを修正

Router:: connect( '/a/', array (
    'plugin'     => 'A' ,
    'controller' => 'index' ,
    'action'     => 'index' )
);
Router:: connect( '/b/', array (
    'plugin'     => 'B' ,
    'controller' => 'index' ,
    'action'     => 'index' )
);
Router:: connect( '/c/', array (
    'plugin'     => 'C' ,
    'controller' => 'index' ,
    'action'     => 'index')
);

これで

http://hogehoge.com/A/

http://hogehoge.com/B/

http://hogehoge.com/C/

みたいにアクセスしてみると、例えばAプラグインを見に行っているのにBプラグインの基底クラスを探しに行ってFatal Errorになる場合がある。

そもそもコントローラーの生成は、lib/Cake/Routing/Dispatcher.phpの_loadControllerで

protected function _loadController($request) {
    $pluginName = $pluginPath = $controller = null;
    if (!empty ($request->params['plugin'])) {
        $pluginName = $controller = Inflector::camelize($request->params['plugin']);
        $pluginPath = $pluginName . '.';
    }
    if (!empty ($request->params['controller'])) {
        $controller = Inflector::camelize($request->params['controller']);
    }
    if ($pluginPath . $controller) {
        $class = $controller . 'Controller';
        App:: uses('AppController' , 'Controller' );
        App:: uses($pluginName . 'AppController' , $pluginPath . 'Controller');
        App:: uses($class, $pluginPath . 'Controller');
        if (class_exists($class)) {
            return $class;
        }
    }
    return false;
}

な感じで処理しているはずなので、URLのリライトが正しく処理できていればプラグイン内の基底コントローラーを含め必要なコントローラーが生成されると思っていた。
実際にApp::usesしている
どうもキャッシュに登録されているプラグインパスを読みに行っていて、それが同名のコントローラーのためエラーになっているらしい。

そもそもURLのリライト自体は正しく処理されているようなので、
問題はCakeのキャッシュだろうと思いapp/tmp/cache/persistent/myapp_cake_core_file_mapを調べてみた。

Array
(
.
.
.
    [plugin.IndexController] => /path/to/app/Plugin/A/Controller/IndexController.php
    [plugin.AAppController] => /path/to/app/Plugin/A/ControllerAAppController.php
.
.
.
)

ここに記載されているキャッシュを見てみると、プラグイン別に同名のコントローラーを持てるようなキャッシュの命名規則になっていないらしい。(AプラグインのIndexコントローラーでもBプラグインのIndexコントローラーでもキャッシュではplugin.IndexControllerになっている)
となると、今回の様な事をやろうとした場合は以下の2つの対応があると思う。

  • キャッシュを無効にする
  • キャッシュを生成している部分を弄る

当然、キャッシュを無効にする対応が一番手っ取り早い。
その場合は、以下のようにcore.phpとかでキャッシュを無効にする。

Configure::write('Cache.disable', false);

もう一つの対応のキャッシュを生成している部分を弄る(キャッシュを有効にしつつ、この問題を対応しようとする)を考えてみると、どうしてもコアの部分を弄らなくてはいけないので、その部分は無理矢理対応しない方が良いかもしれない。

でもプラグイン別に同じコントローラー名使いたい時とか割とあるよなあ。。