Zend_Application (5) 実戦的使い方

モデルについてエントリーを書こうと思っていたら、思わぬPCトラブルでテンションが下がってしまったので、その前にZend_Applicationのプラグインリソースその他についてメモしておきます。
Zend_Applicationを使いこなすポイントは下記の通り

  • ブートストラップ
  • リソース
  • プラグインリソース
  • iniの読み分け
  • モジュール別ブートストラップ

基本的な流れ

  1. ブートストラップで起動する
  2. コントローラーでgetInvokeArg('bootstrap’);
  3. コントローラーでgetResource
  4. コントローラーでgetPluginResource

以前にも書いていますが、基本的な流れとしては、
Zend_Applicationで起動する

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

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

注)↑ここは確認向けにオプションを配列で与えていますが、コンフィグファイルへのパスを単独で与える方がシンプルでわかりやすいです。
コントローラーでブートストラップを取得する

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

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

ブートストラップからリソース、もしくはプラグインリソースを取得する。起動時に設定したiniで定義されたリソース群を呼び出すことができるようになります。

<?php
$resource = $bootstrap->getResource('item');
$pluginResource = $bootstrap->getPluginResource('item');

という形になります。
このリソース管理をモジュール別に行う場合は、Zend_Application(4) モジュール別ブートストラップを使う - noopな日々にあるような感じでモジュール別ブートストラップを取得するメソッドをコントローラーの基底に用意すると使いやすいと思います。

プラグインリソースによる拡張

唐突にプラグインリソースの話を持ち出しましたが、Zend_Applicationではプラグインリソースによる拡張が非常にやりやすくなっています。
プラグインリソースによる拡張を行うには、

というのが基本になります。

プラグインリソースディレクトリを追加する

Zend_Applicationを起動する際に利用するiniファイルでpluginpathsというパラメーターにプラグインリソースを配置するパスをセットすることで、プラグインリソースをZend Frameworkと切り離し、もしくは上書きする形で使うことができます。
pluginpaths.My_PluginResource = /path/to/pluginpath
モジュールで使用するときは、
someModule.pluginpaths.Some_PluginResource = /path/to/module/pluginpath
デフォルトで用意されているプラグインリソース使わずに自前のクラスを使いたいときや、独自のリソース(たとえばモデルとか)を用意するのに使います。

プラグインリソース用のクラスを用意する。

プラグインリソースディレクトリを追加したら、あとはそこにクラスを追加します。
Zend_Application_Resource_ResourceAbstractを継承し、pluginpathsで与えたプレフィックスを付け、リソース名を追加したクラス名で配置します。
必要なメソッドはinit()で、init()の返り値が、
$bootstrap->getResource('hoge’);
で返されます。通常、ここでviewやitemなどのオブジェクトを取得します。
一方、
$bootstrap->getPluginResource('hoge’);
とすることで、プラグインリソースクラスのインスタンスを直接取れるので、プラグインリソースクラスで宣言したメソッドを読んだり、プラグインリソースクラスをサービスロケーターにする時には便利です。プラグインリソースクラスを機能クラスとしてそのまま利用する場合は、init()でreturn $this;しておくとgetResourceから実行できます。*1
また、プラグインリソースクラスでは、getOptionsによって、iniファイルで指定したリソース用のオプションを受け取ることができます。
これで、コントローラーで使用する各種リソースと、リソースの機能、設定を分離することができました。

iniの読み分け

システムの運用方法にもいろいろありますが、bootstrap段階でiniを読み分けられるといくつかのメリットが得られます。
Zend_Applicationのデフォルトでは、envとして、development, staging,productionといった実行環境別にiniのセクションを読み分けるということを行っています。
開発中と公開中で、ロギレベルや接続先などを変えたい場合に便利です。
この機能は、もう少し踏み込むとアプリケーションにも利用できます。たとえば、私は、マルチドメインのアプリケーション管理が必要なのですが、Zendのルーターでhost別にパラメーターを仕込むというよりも、ホスト名でiniを読み分ける方が効率的だと考えています。この方法なら、パラメーターを判定したりする余計な手間がなく、アプリケーションはiniから読んだオプションで実行されるように書けば良いだけとなります。
別の例としては、同じモジュールと基本的なコントローラー等の機能群をモバイルとPCで共有したいが、ビューを作成するときに文字コードを変えたり、Zend_Formのデコレーションを切り替える、フィルタルールを変える時など、各違いをどこかで吸収する必要があります。そういった細かい違いはともかく、ルーティングそのものに手を加えたり、コントローラーの構成自体を切り替えるとしたら条件分岐で実装するのはナンセンスです。こんなときは、iniの読み分けを使うと便利です。PCとモバイルだけではなく、モバイルでも、機種間の違いなどを、Zend_Configの継承機能が使えるので、違いを吸収しやすくなります。
要するに、iniを読み分けるということは、実装された機能群をiniで、完全にカスタマイズできるとも言えます。

iniを読み分ける方法

まず、Zend_ApplicationのBootstrapには、

  • configファイルを追加指定する
  • configファイルにスクリプトを用いる
  • クラスリソースで追加読み込みする

という方法があります。

Zend_Applicationで使える設定形式

Zend_Applicationに渡す設定形式としては、拡張子iniならphp-ini形式、xmlならxml、.phpもしくは.incならphpスクリプトで配列で返すという方法があります。
込み入った処理を行うときは、phpスクリプトで処理できます。

configファイルを追加指定する

Zend_Applicationのオプションでconfigがあります。ここにconfig = hogehoge.iniという具合にすることで、追加のconfigをマージすることができます。環境毎に違うコンフィグを読みたい場合などは、ここで集中管理することができるかもしれません。
うーんでも、私はあまり使いません。

configファイルにスクリプトを用いる

拡張子がincかphpになったファイルを初期の設定ファイルとして指定すると、phpとしてパースして返り値の配列をconfigとして利用されます。
したがって、たとえば、サブドメインやuserAgentでconfigを読み変えて各種設定を切り替えたりしたい場合にはこの方式がわかりやすいです。
たとえば、マルチドメインCMSなどを想定すると、サブドメイン毎にルーティング情報が異なってくる可能性があります。すると、ルーティングの読み込み先をサブドメイン毎のファイルから行いたいという欲求が生まれるはずです。
つまり、Bootstrapに与えるオプションを変更したければ、Zend_Applicationに与えるコンフィグを読みかえるのがベストと、私は思っていますが、どうでしょう・・・まぁ、好みの問題かもしれないけれど。

クラスリソースで追加読み込みする

Bootstrapクラス内で_initConfig()みたいなメソッドを定義しておくことで、自由に追加のオプションをロードできると思います。そういう方法もあり。

多量のconfigは読み分けとキャッシュで

xmlでもiniでもいいんですが、多様な動作を行うシステムでは設定が膨大になりがちです。
すべての可能性を考慮したconfigを読んでおいて、コントローラーでifやswitchを連発するのはメンテナンス性に落ちます。
ですので、初動段階でわかりきっている内容については、config自体を読み分けて余計な判定を機能群に持ち込まないようにした方がgoodですね。
また、各種設定は1ファイルにすべて記述することも可能ですが、可読性が落ちてきます。そこで
http://d.hatena.ne.jp/heavenshell/20090628/1246164962
こちらでも紹介されているように、複数の設定ファイルに分けてしまうのがいいと思います。
ただ、ファイルを分けすぎるとディスクIOあたりに影響がでないかどうか微妙なので、私は設定を読み込んだらまとめてキャッシュしておきます。
また、分割された側では、自分がresourceならresource.を全設定の前に追加するのは面倒なので、マージするときに、キー名をファイル名を一致させるとかいう工夫も含め、複数のファイルに分割すること、iniを読み分けること、それらをキャッシュすることというのを実現したものを作りました。
http://code.google.com/p/zfsubcms/source/browse/trunk/MultiDomain/system/configs/builtin/config.php

うちでは、これで元気に動いてます。

追加したプラグインリソースの使用を宣言する

プラグインリソースクラスをプラグインリソースディレクトリに配置したら、設定ファイルで、
resources.hoge =
という具合に、オプションを書きます。
オプションがない場合は、equalの右辺はカラでいいです。
モジュール別ブートストラップを使う時に
resources.modules =
とやったのと同じ理屈ですね。

プラグインリソースでモデルを扱うメリット

プラグインリソースを使ってモデルを扱うといくつかのメリットがあります。

  • モデルのファクトリを分離できる
  • 注入するブートストラップを設定で切り替えることで、同じコントローラーで別のモデルを利用できる
  • 同じモデルを別のモジュールで利用できる。

まぁ、要するに分離のメリットが得られるわけです。
Zend Frameworkに限りませんが、MVCフレームワークだとコントローラーの具体性がかなり強いのですが、ここまできっちり分離することができると、コントローラーは純粋なウインドウエージェント的に動作させることも可能だと思います。(可能だけれど、そこにメリットがあるかどうかはシステムの粒度次第です。)

*1:使う側でgetPluginResourceよりは、getResourceの方が実装依存が少なくclassResourceへの切り替えも可能なため