CakePHPがコンポーネントを読み込む仕組みを調べてみた

SecurityComponentのチェックにかかってにっちもさっちもいかなくなったんだが、そもそも基本的な動作フローを理解していないのでどこから手を付けていいのか分からない。というわけで、コンポーネントが読み込まれる仕組みを調べてみた。

対象バージョンは1.3.6だけど、2011/05/19時点のgithubのmasterを見てもこのあたりは変わっていないらしい。

■Dispatcherから始める

コケたところでdebug_backtraceを取って、そこからさかのぼってみる。

Dispatcher(cake/dispatcher.php)のdispatchメソッドの最後で$this->_invokeが呼ばれていて、ここから読み始めることにした。

アクションメソッドを呼び出す前に以下の処理があり、ここでコンポーネントの読み込みと初期化を行っているようだ。

<?php
  function _invoke(&$controller, $params) {
    $controller->constructClasses();
    $controller->startupProcess();

■Controllerを読む

Controller(cake/libs/controller/controller.php)の中を見る。まずはControllerの中で定義されているComponent関連と思われるメンバ変数の定義。

<?php
/**
 * Instance of Component used to handle callbacks.
 *
 * @var string
 * @access public
 */
    var $Component = null;

/**
 * Array containing the names of components this controller uses. Component names
 * should not contain the "Component" portion of the classname.
 *
 * Example: `var $components = array('Session', 'RequestHandler', 'Acl');`
 *
 * @var array
 * @access public
 * @link http://book.cakephp.org/view/961/components-helpers-and-uses
 */
    var $components = array('Session');

componentsとComponentは別物という扱いのようだ。componentsに列挙されたコンポーネントを回して、それぞれがコールバックを叩くというイメージを持っていたが、どうやら違うらしい。

Componentがstringということは、callbackを持てるコンポーネントは1つだけで、その名前を入れるとかだろうか。

$this->Componentはコンストラクタの中で以下のように初期化している。

<?php
    $this->Component =& new Component();

…えっと、なんかインスタンス突っ込んでるんだけど「@var string」ってコメントの記述はドキュメントバグでしょうか。


気を取り直して、Dispatcherの_invokeで呼ばれている処理をそれぞれ追ってみる。まずはconstructClasses()から。ここでは、

<?php
    $this->Component->init($this);

してるだけ。そして、startupProcess()の全処理。

<?php
  function startupProcess() {
    $this->Component->initialize($this);
    $this->beforeFilter();
    $this->Component->triggerCallback('startup', $this);
  }

まとめると、beforeFilterで特別なことをしなければ、以下のフローで処理されていることになる。

<?php
$this->Component =& new Component();
$this->Component->init($this);
$this->Component->initialize($this);
$this->Component->triggerCallback('startup', $this);

■Componentを読む

Controller内のComponentとcomponentsの関係が怪しい感じなので、コメントを確認してみると、

<?php
/**
 * Handler for Controller::$components
 *
 * @package       cake
 * @subpackage    cake.cake.libs.controller
 * @link          http://book.cakephp.org/view/993/Components
 */

という、やっぱり怪しいことが書いてある。

SecurityComponentiを確認してみると、Componentを継承しておらず、Componentと同じくObjectを継承している。

Componentクラスはコンポーネントの基底クラスではなく、Controllerがcomponentsを管理する際に必要な処理をまとめるための物らしい。こんなの絶対おかしいよ。

●init(&$controller)

controllerのcomponentsで指定されたコンポーネントを順次ロードする。実際の処理は_loadComponents()が受け持っており、このあたりの実装は相当気持ち悪い。

コンポーネントからコンポーネントを呼ぶことにも対応しており、controllerから直接呼ばれたコンポーネントは$this->_primary[]に入っていく。

$this->_primaryが何者かというと、こういうことらしい。

<?php
/**
 * List of components attached directly to the controller, which callbacks
 * should be executed on.
 *
 * @var object
 * @access protected
 */
    var $_primary = array();

「@var object」って書いておいて、すぐに「var $_primary = array();」で初期化しているが気にしてはいけない。

●initialize(&$controller)

コンポーネントのinitialize()を呼ぶ。

●triggerCallback($callback, &$controller)

$this->_primaryに入っているコンポーネントで$callbackに該当するメソッドが定義されていれば、それを順に呼んでいく。

今追っているフローだと、callbackはstartupなので、各コンポーネントのstartupメソッドを呼んでいる。

前述の「componentsに列挙されたコンポーネントを回して、それぞれがコールバックを叩く」というイメージは間違ってなくて、それを担当するのがComponentだったということ。

ざっくりした流れは追えたので、今日はここまで。