プラグイン

導入

コントローラにはプラグイン機構が組み込まれており、 コントローラの処理中にイベントが発生した際にユーザのコードをコールできます。 フロントコントローラは、プラグインブローカにユーザのプラグインを登録します。 そして、イベントメソッドがコールされた際に、 フロントコントローラに登録されているプラグインをプラグインブローカが実行します。

イベントメソッドは、抽象クラス Zend_Controller_Plugin_Abstract で定義されています。 ユーザが作成するプラグインクラスは、これを継承させます。

  • routeStartup() は、Zend_Controller_Frontルータ をコールしてルートに対するリクエストの評価を始める前にコールされます。

  • routeShutdown() は、 ルータ がリクエストのルーティングを終了した後にコールされます。

  • dispatchLoopStartup() は、Zend_Controller_Front がディスパッチループに入る前にコールされます。

  • preDispatch() は、アクションが ディスパッチャ でディスパッチされる前にコールされます。 このコールバックは、プロキシやフィルタ的な動作をさせることができます。 リクエストの内容を変更してディスパッチフラグをリセット (Zend_Controller_Request_Abstract::setDispatched(false) を使用します) することで、現在のアクションをスキップさせたり置き換えたりできます。

  • postDispatch() は、アクションが ディスパッチャ でディスパッチされた後にコールされます。 このコールバックは、プロキシやフィルタ的な動作をさせることができます。 リクエストの内容を変更してディスパッチフラグをリセット (Zend_Controller_Request_Abstract::setDispatched(false) を使用します) することで、新しいディスパッチ先アクションを指定できます。

  • dispatchLoopShutdown() は、Zend_Controller_Front がディスパッチループを抜けた後にコールされます。

プラグインの書き方

プラグインクラスを書くには、単に抽象クラス Zend_Controller_Plugin_Abstract をインクルードしてそれを継承するだけです。

class MyPlugin extends Zend_Controller_Plugin_Abstract
{
    // ...
}

Zend_Controller_Plugin_Abstract には抽象メソッドはありません。 つまり、上に示したイベントメソッドを、 プラグインクラスでかならず実装しなければならないわけではありません。 プラグインの作者が、必要なものだけを選んで実装できます。

Zend_Controller_Plugin_Abstract では、 リクエストオブジェクトやレスポンスオブジェクトをプラグインから操作できます。 それぞれ、getRequest() メソッドおよび getResponse() メソッドを使用します。

プラグインの使用法

プラグインクラスを登録するには、 Zend_Controller_Front::registerPlugin() をコールします。 これは、いつでも行うことができます。 次の例は、コントローラチェインでプラグインを使用する方法を示すものです。

class MyPlugin extends Zend_Controller_Plugin_Abstract
{
    public function routeStartup(Zend_Controller_Request_Abstract $request)
    {
        $this->getResponse()
             ->appendBody("<p>routeStartup() がコールされました</p>\n");
    }

    public function routeShutdown(Zend_Controller_Request_Abstract $request)
    {
        $this->getResponse()
             ->appendBody("<p>routeShutdown() がコールされました</p>\n");
    }

    public function dispatchLoopStartup(
        Zend_Controller_Request_Abstract $request)
    {
        $this->getResponse()
             ->appendBody("<p>dispatchLoopStartup() がコールされました</p>\n");
    }

    public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
        $this->getResponse()
             ->appendBody("<p>preDispatch() がコールされました</p>\n");
    }

    public function postDispatch(Zend_Controller_Request_Abstract $request)
    {
        $this->getResponse()
             ->appendBody("<p>postDispatch() がコールされました</p>\n");
    }

    public function dispatchLoopShutdown()
    {
        $this->getResponse()
             ->appendBody("<p>dispatchLoopShutdown() がコールされました</p>\n");
    }
}

$front = Zend_Controller_Front::getInstance();
$front->setControllerDirectory('/path/to/controllers')
      ->setRouter(new Zend_Controller_Router_Rewrite())
      ->registerPlugin(new MyPlugin());
$front->dispatch();

他に何かの出力を行うアクションがなく、ひとつのアクションのみがコールされたとしましょう。 上のプラグインは、次のような出力を行います。

<p>routeStartup() がコールされました</p>
<p>routeShutdown() がコールされました</p>
<p>dispatchLoopStartup() がコールされました</p>
<p>preDispatch() がコールされました</p>
<p>postDispatch() がコールされました</p>
<p>dispatchLoopShutdown() がコールされました</p>

注記

プラグインは、フロントコントローラの実行時ならいつでも登録できます。 しかし、プラグインがイベントメソッドを登録しようとしているイベントが終わった後では、 そのメソッドは実行されません。

プラグインの取得と操作

時には、プラグインの登録を解除したりプラグインの情報を取得したいこともあるでしょう。 フロントコントローラには、そのような場合のために次のメソッドが用意されています。

  • getPlugin($class) は、指定したクラス名のプラグインを取得します。 一致するプラグインがない場合は FALSE を返します。 同じクラス名のプラグインが複数登録されている場合は、結果を配列で返します。

  • getPlugins() は、プラグインスタック全体を取得します。

  • unregisterPlugin($plugin) は、プラグインをスタックから登録解除します。 パラメータには、プラグインオブジェクト自体かそのクラス名を渡します。 クラス名を渡すと、一致するプラグインがすべて削除されます。

標準の配布パッケージに含まれるプラグイン

Zend Framework の配布パッケージには、 エラー処理用のプラグインが標準で組み込まれています。

ActionStack(日本語)

ActionStack プラグインは、リクエストをスタックで管理します。 postDispatch プラグインとして動作します。 現在のリクエストオブジェクトで既に転送先が指定されている (別のアクションがコールされている) 場合は、何も行いません。 転送されていない場合はスタックの最上位の要素を取り出し、 そのリクエストが指すアクションに転送します。 スタックの処理は LIFO (後入れ先出し) 方式で行います。

このプラグインをフロントコントローラから取得するには、 Zend_Controller_Front::getPlugin('Zend_Controller_Plugin_ActionStack') とします。プラグインオブジェクトを取得したら、 さまざまな仕組みを利用できるようになります。

  • getRegistry() および setRegistry()ActionStack は、内部的に Zend_Registry のインスタンスを使ってスタックを管理しています。 別のレジストリインスタンスでこれを置き換えたり、 インスタンスを取得したりする際にこれらのアクセサを使用します。

  • getRegistryKey() および setRegistryKey()。 これらは、スタックに格納する際にレジストリのどのキーを使用するかを指定します。 デフォルト値は 'Zend_Controller_Plugin_ActionStack' です。

  • getStack() は、 そのアクションのスタック全体を取得します。

  • pushStack() および popStack() は、それぞれスタックへの追加とスタックからの取り出しを行います。 pushStack() にはリクエストオブジェクトを渡します。

さらに forward() メソッドが存在します。 このメソッドにはリクエストオブジェクトを渡し、 フロントコントローラにおける現在のリクエストオブジェクトの状態を ここで渡したオブジェクトの状態に設定します。 そして、リクエストを未ディスパッチ状態に戻します (ディスパッチループの次の処理に強制的に進ませます)。

Zend_Controller_Plugin_ErrorHandler(日本語)

Zend_Controller_Plugin_ErrorHandler は、アプリケーションからスローされた例外を処理するためのプラグインです。 たとえば、指定したコントローラやアクションが見つからないといったエラーを処理します。 これは、MVC の例外についてのセクション で説明したメソッド群の代わりとして使用できます。

このプラグインが主に対象としているのは、次のような例外です。

  • 一致したルートが見つからない場合に発生する例外

  • コントローラやアクションメソッドが見つからない場合に発生する例外

  • アクションコントローラ内で発生する例外

言い換えると、ErrorHandler プラグインが想定しているのは、HTTP 404 型のエラー (ページが存在しない) と 500 型のエラー (内部エラー) ということになります。 他のプラグインで発生した例外の処理は、想定していません。

デフォルトでは、Zend_Controller_Plugin_ErrorHandler はデフォルトモジュールの ErrorController::errorAction() に処理を転送します。これを変更するには、以下のようなアクセス用メソッドを使用します。

  • setErrorHandlerModule() は、 使用するコントローラモジュール名を設定します。

  • setErrorHandlerController() は、 使用するコントローラを設定します。

  • setErrorHandlerAction() は、 使用するコントローラアクションを設定します。

  • setErrorHandler() は連想配列を受け取ります。 この連想配列のキーには 'module'、'controller' あるいは 'action' を指定することができ、 それぞれ対応する値を設定します。

さらに、コンストラクタの引数として連想配列を渡すこともできます。 この場合、その配列がそのまま setErrorHandler() に渡されます。

Zend_Controller_Plugin_ErrorHandlerpostDispatch() フックとして登録され、 レスポンスオブジェクト に格納された例外を確認します。もし何かの例外が見つかったら、 事前に登録されているエラーハンドラアクションに処理を転送します。

エラーハンドラへのディスパッチ中に例外が発生した場合は、 このプラグインはフロントコントローラに例外をスローします。 その際に、レスポンスオブジェクトに格納された直近の例外を再度スローします。

404 ハンドラとしての ErrorHandler の使用

ErrorHandler プラグインが捕捉するのは、 アプリケーションのエラーだけではありません。 コントローラチェインが次のコントローラクラスやアクションメソッドを 見つけられなかった場合に、404 ハンドラとして動作させることもできます。 そのためには、エラーコントローラ内で例外の型を調べる必要があります。

捕捉した例外は、リクエストで登録したオブジェクトの中に記録されます。 これを取得するには、 Zend_Controller_Action::_getParam('error_handler') を使用します。

class ErrorController extends Zend_Controller_Action
{
    public function errorAction()
    {
        $errors = $this->_getParam('error_handler');
    }
}

エラーオブジェクトを取得したら、次に $errors->type でその型を調べます。 これは、次のいずれかとなります。

  • Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE は、一致したルートが見つからなかったことを表します。

  • Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER は、コントローラが見つからなかったことを表します。

  • Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION は、アクションが見つからなかったことを表します。

  • Zend_Controller_Plugin_ErrorHandler::EXCEPTION_OTHER は、その他の例外を表します。

最初の3つの型であった場合に、404 ページを返せばいいわけです。

class ErrorController extends Zend_Controller_Action
{
    public function errorAction()
    {
        $errors = $this->_getParam('error_handler');

        switch ($errors->type) {
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
                // 404 エラー -- コントローラあるいはアクションが見つかりません
                $this->getResponse()
                     ->setRawHeader('HTTP/1.1 404 Not Found');

                // ... 何か、表示する内容を作成します ...
                break;
            default:
                // アプリケーションのエラー。エラーページを表示しますが、
                // ステータスコードは変更しません
                break;
        }
    }
}

エラーハンドラで発生した礼儀を取得するには、 error_handler オブジェクトのプロパティ exception を使用します。

public function errorAction()
{
        $errors = $this->_getParam('error_handler');

        switch ($errors->type) {
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
                // 404 エラー -- コントローラあるいはアクションが見つかりません
                $this->getResponse()
                     ->setRawHeader('HTTP/1.1 404 Not Found');

                // ... 何か、表示する内容を作成します ...
                break;
            default:
                // アプリケーションのエラー。エラーページを表示しますが、
                // ステータスコードは変更しません

                // ...

                // 例外をログに記録します
                $exception = $errors->exception;
                $log = new Zend_Log(
                    new Zend_Log_Writer_Stream(
                        '/tmp/applicationException.log'
                    )
                );
                $log->debug($exception->getMessage() . "\n" .
                            $exception->getTraceAsString());
                break;
        }
}
前回のレンダリング結果の扱い

ひとつのリクエストで複数のアクションにディスパッチする場合、 あるいはアクション内で render() を複数回コールする場合などは、 レスポンスオブジェクト内にすでに内容が格納されていることがあります。 これをうまく処理しないと、本当に必要な内容と それ以外の内容が混じってしまう恐れがあります。

そのような場合にページの中でエラー処理をしたい場合は、 特に何も手を加える必要はありません。 そのような内容をレンダリングしたくないという場合は、 ビューのレンダリング前に以前の内容を消去しておく必要があります。

$this->getResponse()->clearBody();
プラグインの使用例

例155 標準的な使用法

$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new Zend_Controller_Plugin_ErrorHandler());

例156 別のエラーハンドラの設定

$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new Zend_Controller_Plugin_ErrorHandler(array(
    'module'     => 'mystuff',
    'controller' => 'static',
    'action'     => 'error'
)));

例157 アクセス用メソッドの使用

$plugin = new Zend_Controller_Plugin_ErrorHandler();
$plugin->setErrorHandlerModule('mystuff')
       ->setErrorHandlerController('static')
       ->setErrorHandlerAction('error');

$front = Zend_Controller_Front::getInstance();
$front->registerPlugin($plugin);

エラーコントローラの例

エラーハンドラプラグインを使用するには、 エラーコントローラが必要です。以下にシンプルな例を示します。

class ErrorController extends Zend_Controller_Action
{
    public function errorAction()
    {
        $errors = $this->_getParam('error_handler');

        switch ($errors->type) {
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
                // 404 エラー -- コントローラあるいはアクションが見つかりません
                $this->getResponse()->setRawHeader('HTTP/1.1 404 Not Found');

                $content =<<<EOH
<h1>エラー!</h1>
<p>そのページは存在しません。</p>
EOH;
                break;
            default:
                // アプリケーションのエラー
                $content =<<<EOH
<h1>エラー!</h1>
<p>予期せぬエラーが発生しました。後でもう一度お試しください。</p>
EOH;
                break;
        }

        // 前回の内容を消去します
        $this->getResponse()->clearBody();

        $this->view->content = $content;
    }
}

Zend_Controller_Plugin_PutHandler(日本語)

Zend_Controller_Plugin_PutHandlerは、 まるで POST リクエスト・ボディのようなリクエスト・パラメータに PUT リクエスト・ボディを配置するために、 ドロップイン・プラグインを提供します。 それはリクエストを調べます、そして、 PUT ならば、 生の PUT ボディを解析してリクエストに配置されるパラメータの配列にするためにparse_strを使います。 例えば、

PUT /notes/5.xml HTTP/1.1

title=Hello&body=World

'title' 及び 'body' パラメータを通常のリクエスト・パラメータとして受け取るために、 プラグインを登録します:

$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new Zend_Controller_Plugin_PutHandler());

そして、コントローラ内でリクエストから PUT ボディー・パラメータに名前によるアクセスができます:

...
public function putAction()
{
    $title = $this->getRequest()->getParam('title'); // $title = "Hello"
    $body = $this->getRequest()->getParam('body'); // $body = "World"
}
...