本来ならpluginで拡張できるコアな部分ってControllerとModelだけなんだけど、エラーのところもpluginで拡張したかったので、強引に弄ってみた

CakePHP1系でのお話。
本来ならpluginで拡張できるコアな部分ってControllerとModelだけなんだけど、エラーのところもpluginで拡張したかったので、強引に弄ってみた。
(ホントに強引なんであんまり参考にはならないと思います。)

前提としてこんな構成を想定しています

app
└──plugins
    └──hoge
        ├──hoge_app_controller.php
        ├──hoge_app_error.php
        ├──hoge_app_model.php
        ├──controllers
        ├──models
        └──views
          └──layouts
            └──default.ctp

1. 拡張したControllerにcakeError関数をオーバーライドしてみる

通常だとcakeError関数はObjectクラス内で設定されていてこんな感じになっている。

function cakeError($method, $messages = array()) {
    if (!class_exists('ErrorHandler' )) {
        App:: import('Core', 'Error');
        if (file_exists(APP . 'error.php' )) {
            include_once (APP . 'error.php' );
        } elseif (file_exists(APP . 'app_error.php' )) {
            include_once (APP . 'app_error.php' );
        }
    }

    if (class_exists('AppError' )) {
        $error = new AppError($method, $messages);
    } else {
        $error = new ErrorHandler($method, $messages);
    }
    return $error;
}

で、これだとそもそもpluginに記述した拡張エラークラスは拾わないので、プラグイン内に設置した拡張エラーハンドリンククラスも有効にするように、こんな感じに修正。

class HogeAppController extends AppController {
    public function cakeError($method, $messages = array()) {
        if (!class_exists('ErrorHandler' )) {
            App:: import('Core', 'Error');
            if (file_exists(APP . 'plugins' . DS . $this-> plugin . DS . $this->plugin . '_app_error.php' )) {
                include_once (APP . 'plugins' . DS . $this-> plugin . DS . $this->plugin  . '_app_error.php' );
            }

            elseif (file_exists(APP . 'error.php' )) {
                include_once (APP . 'error.php' );
            }
            elseif (file_exists(APP . 'app_error.php' )) {
                include_once (APP . 'app_error.php' );
            }
        }
        if (!is_null($this-> plugin) && class_exists(ucfirst($this-> plugin) . 'AppError')) {
            $errorClass = ucfirst($this-> plugin) . 'AppError';
            $error = new $errorClass($method, $messages, $this->plugin);
        }
        elseif (class_exists('AppError' )) {
            $error = new AppError($method, $messages);
        }
        else {
            $error = new ErrorHandler($method, $messages);
        }
        return $error;
    }
}

これでplugin内に設置した拡張エラークラスが有効になるようになった。

2. 拡張エラークラス用のファイルを設置

/app/plugins/hoge配下に、拡張エラークラス(hoge_app_error.php)を作成

3. エラー処理時に利用するコントローラー(CakeErrorController)をプラグインように別で用意

通常のCakeErrorControllerを利用すると、継承されているコントローラーはAppControllerになる。
今回はプラグインで用意したHogeAppControllerを継承したいので、通常のCakeErrorControllerとは別で用意する。
(ここでは仮にAppendCakeErrorControllerとする)

class AppendCakeErrorController extends HogeAppController {
    var $name = 'AppendCakeError';
    var $uses = array();
    function __construct() {
        parent::__construct();
        $this->_set(Router::getPaths());
        $this-> params = Router:: getParams();
        $this->constructClasses();
        $this-> Component->initialize($this);
        $this->_set( array('cacheAction' => false, 'viewPath' => 'errors'));
    }
}

4. 拡張エラークラスを用意

プラグイン内で利用するエラークラスを用意し、ErrorHandlerクラスの__construct関数を持ってきて、プラグイン内のもの利用するように修正。
(通常のErrorHandlerを拡張したものと違い、コンストラクタの第3引数にプラグインの名前を入力するのは、プラグイン内のlayoutを利用するようにするため)

class HogeAppError extends ErrorHandler {

        function __construct($method, $messages, $pluginName = null) {
              App:: import('Core', 'Sanitize');
               static $__previousError = null;

               if ($__previousError != array($method, $messages)) {
                     $__previousError = array($method, $messages);
                     $this-> controller =& new AppendCakeErrorController();
              } else {
                     $this-> controller =& new Controller();
                     $this-> controller->viewPath = 'errors' ;
              }
               if (!is_null($pluginName)) {
                  $this-> controller->plugin = $pluginName;
              }
              $options = array('escape' => false);
              $messages = Sanitize:: clean($messages, $options);

               if (!isset ($messages[0])) {
                     $messages = array($messages);
              }

               if (method_exists($this->controller , 'apperror' )) {
                      return $this->controller ->appError($method, $messages);
              }

               if (!in_array(strtolower($method), array_map('strtolower', get_class_methods($this)))) {
                     $method = 'error';
              }

               if ($method !== 'error' ) {
                      if (Configure::read( 'debug') == 0) {
                           $parentClass = get_parent_class($this);
                            if (strtolower($parentClass) != 'errorhandler' ) {
                                  $method = 'error404';
                           }
                           $parentMethods = array_map( 'strtolower', get_class_methods($parentClass));
                            if (in_array(strtolower($method), $parentMethods)) {
                                  $method = 'error404';
                           }
                            if (isset ($messages[0]['code']) && $messages[0]['code'] == 500) {
                                  $method = 'error500';
                           }
                     }
              }
              $this->dispatchMethod($method, $messages);
              $this->_stop();
       }
}

これでとりあえずプラグインに設置した拡張エラークラスを見に行くようになった。