7.10. プラグイン

7.10.1. 導入

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

イベントメソッドは、抽象クラス 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 がディスパッチループを抜けた後にコールされます。

7.10.2. プラグインの書き方

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

<?php
require_once 'Zend/Controller/Plugin/Abstract.php';

class MyPlugin extends Zend_Controller_Plugin_Abstract
{
    // ...
}
?>

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

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

7.10.3. プラグインの使用法

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

<?php
require_once 'Zend/Controller/Front.php';
require_once 'Zend/Controller/Router.php';
require_once 'Zend/Controller/Plugin/Abstract.php';

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

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

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

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

    public function postDispatch($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>
[注意] 注意
プラグインは、フロントコントローラの実行時ならいつでも登録することができます。 しかし、プラグインがイベントメソッドを登録しようとしているイベントが終わった後では、 そのメソッドは実行されません。

7.10.4. プラグインの取得と操作

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

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

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

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

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

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

7.10.5.1. Zend_Controller_Plugins_ErrorHandler

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

<?php
class ErrorController extends Zend_Controller_Action
{
    public function errorAction()
    {
        $errors = $this->_getRequest('error_handler');

        switch ($errors->type) {
            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;
        }
    }
}
?>
7.10.5.1.2. プラグインの使用例

例 7.11. 標準的な使用法

<?php
require_once 'Zend/Controller/Front.php';
require_once 'Zend/Controller/Plugin/ErrorHandler.php';

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

例 7.12. 別のエラーハンドラの設定

<?php
require_once 'Zend/Controller/Front.php';
require_once 'Zend/Controller/Plugin/ErrorHandler.php';

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

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

<?php
require_once 'Zend/Controller/Front.php';
require_once 'Zend/Controller/Plugin/ErrorHandler.php';

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

$front = Zend_Controller_Front::getInstance();
$front->registerPlugin($plugin);
?>
7.10.5.1.3. エラーコントローラの例

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

<?php
class ErrorController extends Zend_Controller_Action
{
    public function errorAction()
    {
        $errors = $this->_getRequest('error_handler');

        switch ($errors->type) {
            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->initView();
        $this->view->content = $content;
        $this->render();
    }
}
?>