プラグインのコントローラがコールされた場合にのみ例外レイアウトを変更する方法

CakePHP のプラグインから、エラー画面のレイアウトファイルを変更したいと思って調べたのでメモ。プラグイン限定としたところ、ちょっと手こずりました。

※ なお内容はCakeEventを利用するため 2.1系 以上です。

やりたかったこと

  1. プラグインのコントローラ呼び出し時のエラーの際に、独自のエラー用のレイアウトで表示させたい。
  2. 極力アプリケーションファイルに変更を加えない。

例として、”MyPlugin” という名前のプラグインを作成し、プラグインのコントローラがリクエストされた場合のエラーに関して MyPlugin.View/Layout/error.ctp というエラー用のレイアウトファイルで出力することを目指すことにします。

こうしたら出来る

ステップ1.

プラグインの bootstrap に設定が必要になるので、まずは app/Config/bootstrap.php で MyPlugin ロード時に bootstrap を読みこませてください。

// app/Config/bootstrap.php
CakePlugin::load('MyPlugin' => array('bootstrap' => true));

ステップ2.

つぎに、MyPlugin.Config/bootstrap.php に以下のように記述して、条件を満たした場合にのみコントローラのレイアウトを変更します。

※グローバル関数に書いてますが、気になる方はコールバックを適当に書きなおしてください

App::uses('CakeEventManager', 'Event');

// 'Controller.startup' イベントに下記のグローバル関数をロード(アタッチ)している
// CakeEventManager::load() の第一引数には php のコーラブルなものならなんでもOK
// CakeEventManager::attach() は @deprecated となっていて、 load() への移行が推奨されています。(2013/06/17 追記)
CakeEventManager::instance()->load('myPluginCustomErrorLayout', 'Controller.startup');

function myPluginCustomErrorLayout(CakeEvent $event) {
	// $event->subject の判定を行い、CakeErrorController で、
	// 且つリクエストのプラグインパラメータが MyPlugin の場合
	// コントローラのレイアウトを変更する
	if ($event->subject instanceof Controller &&
		$event->subject->name === 'CakeError' &&
		$event->subject->request->params['plugin'] === 'my_plugin') {
		$event->subject->layout = 'MyPlugin.error';
	}
}

イベントシステムは色々と楽しい

おまけ:アプリケーション全体で変更したい場合

ついでにアプリケーション全体でエラー画面のレイアウトを変更したい場合もメモ。

方法を大きく3つに分類しました。App側でエラー画面をコントロールする方法は参考になるブログがいくつも有りましたのでそちらを紹介します。ただし、最後の方法は(探し方が悪かったのかもしれませんが)参考になる記事が見つかりませんでしたので自分なりに書いたものです。

例として app/View/Layout/error.ctp を使用したレンダリングを行うとします。

方法1 例外レンダラの変更と実装

例外をレンダリングするクラスを変更し、新たに実装します。

上記のエントリで詳しく解説されていますのでそちらをご覧ください。
自分なりに書こうと思いましたが気持ちが折れました(´・ω・`)
済みません。

  • メリット: コントローラの呼び出しを含めて、描画ロジック全体をがっつりカスタマイズできる。例外が発生しない限りオーバーヘッド無し。例外が発生した場合は実装によりけり。
  • デメリット:設置がちょっと大変かもしれない。コアファイルの設定を確認しないと処理が分からない。

方法2 CakeErrorController の作成

コアにある Controller/CakeErrorController.php をアプリケションのコントローラディレクトリにコピー。
そして、そのプロパティを変更する。

class CakeErrorController extends AppController {

	// ここにレイアウト名を記述する
	public $layout = 'error';

	// 以下、略。全部コピーしてくださいね
  • メリット:設置がシンプル。アプリケーションのコントローラディレクトリを覗いただけで何かやってることが分かる。オーバーヘッド無し。
  • デメリット:バージョンアップの際に、本体のCakeErrorController の変更を常に追跡する必要がある。

方法3 AppController::beforeRender の記述

追加ファイルなしの一番お手軽バージョンと思うのがこちら。

class AppController extends Controller {
	public function beforeRender() {
		if ($this->name === 'CakeError') {
			$this->layout = 'error';
		}
	}
}
  • メリット:とにかく設置が楽。AppController 内の一般的な記述なのでメンテナンス時にも、まぁ見落とさないように思われる。コアの変更の影響もほとんど無いでしょう。
  • デメリット:通常の呼び出しの際にもいちいち判定を行うので若干のオーバーヘッドが生じる。

というわけで、それぞれの特徴に応じて適用されるのが宜しいかと。個人的には「方法2」が一番しっくりくる。