Zend_Application(4) モジュール別ブートストラップを使う

ZF 1.8.1がリリースされて、Zend_Applicationの全容が見えてきた感があります。内部的には、微妙なところも散在しているようなんですが、十分実用的になったと思います。
Zend_Applicationを使う大きなメリットの一つにモジュール別ブートストラップがあります。
入門の門をとりあえず叩いてみたのでまとめておきます。今回試したのは、

  • ブートストラップを配置する
  • モジュール内のオートロードを試す
  • モジュール内リソースの作成と参照を試す

の3点です。

概要

モジュール内のオートロードについては、id:sasezakiさんのZend_Application_Module_Autoloaderで、モジュールロードを手っ取り早く(デフォルトにそって)やる場合のメモ - 例えば、PHPを使うにまとめられていますので、単独で使う場合はそちらをご参照ください。Zend_Applicationのモジュール別ブートストラップを配置すると、このオートロード設定を自動で行ってくれます。また、AutoLoaderに細かい設定を加えるときは、id:heavenshellさんのZend_Application を使う - Memoに記述がありますので、合わせてご参照ください。モジュール別ブートストラップは、大元のブートストラップを使っていない時にも完全な置き換えになるような構成になっているようなのですが、やはり、システム全体をZend_Applicationを使って起動しているという前提の方がわかりやすいです。
モジュール別でない、グローバルなリソース参照については、拙エントリのZend_Application(3) リソースコンテナをアクションコントローラー内から使う方法 - noopな日々で軽く触れてあります。

ブートストラップを配置する

まず、システム全体の起動を下記のように行うとき、オプションにmodulesを追加しておくとモジュール別ブートストラップを自動的に読みに行ってくれます。Zend_Applicationへの設定方法は多彩なので詳しくはマニュアルをみてください。多分、resourcesにmodulesをキーとして与えるのがテスト用としてはてっとり早い気がします。
グローバル用のBootstrap.phpはZend_Application_Bootstrap_Bootstrapを継承した空のクラスでもテストできます。

<?php
    $application = new Zend_Application(
        $env,
        array(
            'bootstrap' => '/path/to/Bootstrap.php',
            'resources' => array('modules' => array()),
        )
    );

    $application->bootstrap();
    $application->run();

次に、モジュール別ブートストラップを配置します。モジュールディレクトリ直下に、Bootstrap.phpというファイル名でZend_Application_Module_Bootstrapを継承した下記のようなクラスを配置します。ここではモジュール名をHogeとしました。中身はカラでもいいんですが、ここでは_initFugaメソッドを追加しておきました。

<?php
class Hoge_Bootstrap extends Zend_Application_Module_Bootstrap
{
    protected function _initFuga()
    {
        $instance = new stdClass();
        $instance->test = 'fuga';
        return $instance;
    }
}

モジュール内のオートロードを試す

上記のようにブートストラップを配置しておくと、モジュール別オートローダーを自動的にセットしてくれます。想定済みのディレクトリ構成にしてある場合には、オートロードは非常に簡単に処理できます。
ためしに、HogeモジュールのmodelsディレクトリにTest.phpという名前でHoge_Model_Testというクラスを置いておくと、下記のようにincludeなしにクラスが使えるようになります。おそらく、別モジュールからクラスを使うこともできると思います。

<?php
//たとえばアクションコントローラー内
    public function indexAction()
    {
        $test = new Hoge_Model_Test();
    }

V1.7時代のオートローダーも便利だったのですが、指定パス以下にクラスを整然と配置する必要があったのですが、モジュール別オートローダーがあるとかなり柔軟にオートロード出来ます。インクルードするためにパスをハードコーディングしたり、グローバル定数にパスをセットしたりといった開発時の決めごとを軽減することができます。

モジュール内リソースの作成と参照を試す

疎結合フレームワークでは、アプリケーション構造内でのインスタンスの管理、たとえば同じ名前でもモジュール間で干渉しないようにしたい、でも、疎結合なコントローラー間で同じインスタンスを使いたいといったニーズに対して、合理的な方法を模索する必要があります。
Zend_ApplicationのDIコンテナはそれに対するベストプラクティスの一つになっているように思います。Zend_ApplicationはDIコンテナをフロントコントローラーのparamに設定しているので、アクションコントローラではgetInvokeArgで取得できます。これはまさに、注入された変数に対する参照なので理にかなっています。

<?php
    public function indexAction()
    {
        // testing class auto loading
        // $test = new Hoge_Model_Test();
        // Zend_Debug::dump($test);

        // getting global bootstrap
        $bootstrap = $this->getInvokeArg('bootstrap');

        // get modulesbootstrap broker and get my bootstrap
        $moduleBootstrap = $bootstrap->getResource('Modules')->Hoge;

        Zend_Debug::dump($moduleBootstrap->getResource('fuga'));

    }

ここでは、まずgetInvokeArgでグローバルのブートストラップを取得し、そこからmodulesリソースを取得しています。*1
modulesリソースはArrayObjectでモジュール別ブートストラップのコレクションになっていますので、その中から自分のモジュール名を指定して、モジュールのブートストラップを取得しています。この部分は、基底クラス等でgetMyBootstrapとかのメソッドを作っておいて、自動的に処理するとすっきりするかも。
次に、getResouce('fuga')で上記した、_initFugaメソッドで返されたインスタンスを取得できます。モジュール別ブートストラップは初期化を行うとともに、モジュールスコープ内でのインスタンス管理にも使えるということになります。_initFuga形式はクラスメソッドによるリソースのロードですが、リソースロード用のクラスを用意するプラグイン形式のリソースのロードも可能です。
_initFuga等の初期化メソッドやプラグインリソースにしておくと、アプリケーションの初期化時に、モジュールに関係なく毎回動作することになりますので、他のモジュールにルーティングされた時は使わないとか、先にルーティングを終了してから呼ばれたいなど、初期化を遅らせたい時は、ブートストラップに、getFooなどのパブリックメソッドを定義して、そこで適切に初期化する方法も考えられます。たとえば、ブートストラップ内で、getForm($name)などを作ってフォームへの参照をブートストラップで管理するといった方法も考えられるでしょう。実際、オートロードは便利ですが、使わないモジュールの初期化まで行う必要があるのか疑問です。

インスタンス管理

コントローラーからモデルを参照する方法としては、

  • コントローラー毎にモデルクラスをインスタンス
  • レジストリに保存
  • アクションヘルパーやプラグインをブローカーにする
  • フロントコントローラーをカスタマイズしてサービスロケーターにする

など、いろんな工夫があったと思うのですが、コントローラー毎ではスコープが小さすぎ、レジストリではグローバル過ぎ、アクションヘルパーやプラグインではモジュール間で横断しすぎていたわけですが、モジュール別ブートストラップが加わったことで、モジュールというスコープで管理したいことに関してはすべてそこに任せるという実装ができるようになりました。

モジュール別ブートストラップによるマルチアプリケーション

モジュール別のブートストラップはアプリケーションの初期化にも使えるので、システムの配置はそのままで、エントリーポイント毎に別のモジュールを起動させるというような新しいパラダイムが生まれています。これについては今のところ個人的にニーズがないので試すつもりはないんですが、その辺も考慮して設計しておくと、あとで融通が利いていいかもしれません。

*1:起動時にZend_Applicationでmodulesキーをリソースとして指定してある必要があります