Zend\Mailで日本語(ISO-2022-JP)メールを送る

ZendFramework2.0でメール送信を行いました。
公式ドキュメントに加えて、下記ブログは参考になります。私はこの通りにはしませんが、ひとつの方法として参考にしました。
http://www.michaelgallego.fr/blog/?p=208

日本語メールは?

テストした環境はmb_stringが使えて、
mbstring.internal_encoding = UTF-8
mbstring.language = Japanese
な環境です。
UTF-8でメールを送る場合、ほとんど問題は起きず、そのまま何もしなくても日本語のsubjectやアドレスの注釈が使えます。ほとんど、と書いたのは、その日本語の中に、たとえば、半角カッコを含んだ場合に、メールサーバーによってはヘッダを解釈してアドレス区切りと理解し、ドメイン名を追加されたりで壊れてしまうケースがあるためです。(どちらに問題があるかについてはよく調べていません!)

JISで送る

さて、スクリプトUTF-8で書いている場合、データのやり取りのほとんどをUTF-8で統一しているという前提とします。
内輪で使う場合は、ほとんどのメールソフトはUTF-8に対応しているので問題ないですが、不特定の顧客に対して送りつけるにはJISで送りたいケースもあると思います。
この場合、Zend Framework2.0では、
$message->setEncoding('ISO-2022-JP');
としてあげれば、ドキュメント上ではできそうですが、勝手に文字コード変換は行わない(行うべきではない)ので個別にデータをJISに文字コード変換してから与える必要があり、それで十分にも見えます。
ところが、よくある文字列でも、JIS変換するとUTF-8上で処理するとかなりの数のカッコ相当の文字が入り、Zend\Mimeでは文字コードを意識しないのでエンコードしてもカッコが残ってしまいます。(内部エンコーディングを変えれば問題が解消するかということについては、どのみち筋が悪いので試していません。)カッコが残ったままsmtpトランスポートに乗せると、UTF-8では稀だったトラブルがかなりの確率で発生することになります。
そこで、対処としては、アドレスを設定する際に、mb_encode_mimeheaderでエンコードしておくこと。(本文については、mb_convert_encodingでよいでしょう。)
$message->setFrom('from@example.com', mb_encode_mimeheader("日本語も(大丈夫)あいうえお", 'JIS', 'Q'));
次に、setEncodingを省略するとUTF-8エンコードしてしまいますので、
$message->setEncoding('ASCII');
として、Zend\Mimeによるエンコードをキャンセルします。
mb_encode_mimeheaderにはmb_encode_mimeheader固有の問題がありますし、UTF-8からJISへの変換では脱落する文字もあり、変換用のライブラリを持っているケースでは、それらを通すような中間クラスを用意することになると思いますが。

たとえばHTMLメールとか

$html = $this->render('mail/receipt', $data);
のように、viewスクリプトからHTMLを育成したとします。

$txtPart = new \Zend\Mime\Part('');
$txtPart->type = "text/plain";
$htPart = new \Zend\Mime\Part(mb_convert_encoding($html, 'JIS'));
$htPart->type = "text/html";
$htPart->charset = "ISO-2022-JP";
$htPart->encoding = "7bit";
$body = new \Zend\Mime\Message();
$body->setParts(array($txtPart, $htPart));

これで、
$message->setEncoding('ASCII');
としている場合でも、JISコードで、charset指定のあるメッセージbodyを作れます。
必要に応じて、language指定なども。

ZendFramework 2.0でバリデータのバリデーションメッセージをカスタマイズするメモ

  1. translatorで翻訳する
  2. translatorのテキストドメインを使う
  3. 個別のメッセージを設定する
  4. Zend\Formで使う

translatorで翻訳する。

デフォルトのバリデーションメッセージを翻訳してみます。
http://framework.zend.com/manual/2.0/en/modules/zend.validator.messages.html#using-pre-translated-validation-messagesv
ドキュメントにあるとおりですが、SkeltonApplicationと同様にtranslatorをセットアップすることで、translatorをServiceManager(ServiceLocator)から取得し、
Zend\Validator\AbstractValidator::setDefaultTranslator($translator);
とセットするコードをどこかに書くことになりそうです。
ZF2になって、Validationサービスでも立っているのかと思いましたが、このあたりは従来通りのようです。

translatorでテキストドメインを使う。

翻訳ファイルが巨大になるとメンテナンスが面倒になりますので、translatorファイルをバリデーションメッセージ用に分割してもよさそう。
translator側でテキストドメイン用の設定をするのはZend\I18nのドキュメントにあるとおりで、バリデーター側にその情報を伝えます。
validatorのオプションで'translatorTextDomain'をキーにテキストドメインを設定します。

個別のメッセージを使う。

基本的には組み込みのバリデーションメッセージを翻訳を利用しますが、お客様向けのメッセージとしてはサービス用の内容を含んでいないので不親切です。そこで、個別のメッセージを設定します。
APIドキュメントを読むと、
$validator->setMessage($message, $key);
という具合に、キーを指定することで発生したエラーの種類毎にメッセージテンプレートを設定できます。翻訳ファイルはこのキー名でもメッセージでも翻訳可能です。
InputFilterを設定から作る際、InputFilterのfactoryはこの点、完全対応しているわけではなく、オプションの'message'キーで設定するとそのバリデーターチェーンのエラーメッセージを完全に置き換えられます。これはケースバイケースで、エラーのバリエーションが1つでよいときはこれで十分でしょう。
種類が多様になる際は、setMessageメソッド経由で設定することになります。
サンプルはベタですが、こういった処理は、DIのインスタンスマネージャーを使うと管理しやすくなると思います。

Zend\Formで使う

Zend\Formの$form->isValid()で使うInputFilterも同様ですが、いくつかの方法があるため利用目的に合わせて検討する必要があります。

  • InputFilterAwareInterfaceなエンティティ(もしくはオブジェクト)をバインドする
  • Elementのデフォルトのバリデーションを有効にする
  • 個別に定義する

下記では、$form->getInputFilter()でinputFilterを取得してinputFilterにaddしています。

Zend\Formとの連携で注意すること

Zend\Formはデフォルトで、各エレメントが持っているInputSpecificationからInputFilterを構築するようになっています。HTML5の要素など利便性が高いデフォルトのフィルターとバリデーションがセットされているので、これは通常は使いたい機能です。このデフォルトのInputFilter設定は、
$form->getInputFilter()がコールされたときに、その要素のためのInputFilterにマージされます。
フォームのバリデーションではgetInputFilterがコールされるので、デフォルトにバリデーションが先頭に入ってくることになります。
この動作を停止するには、フォームクラスを書く際に、$useInputFilterDefaults = false;とすればよいのですが、これは習慣的にやらないほうがいいかなと。
さて、Zend\InputFilter\Inputが個別のエレメント用のInputFilterになりますが、この動作として、required=>true時は、isValid()コール時にバリデーションチェーンの先頭がNotEmptyバリデーターでなければ、設定なしNotEmptyを'break_chain_on_failure' => trueで追加し、required=>falseの際には値が空の場合は、validとしてバリデーションを終了します。結果的にオプション設定したNotEmptyバリデータを設定時に追加しても意味をなしません。(何を言ってるかわからないと思いますが・・・!)NotEmptyバリデータのメッセージを変更したいときは、下記のパッチを充ててInputFilterにNotEmptyバリデーターを設定するとできます。上のサンプルはこれが前提になっています。
https://github.com/noopable/zf2/commit/4e0bec36a95a334c71a564b2496eedbebc8b0c2a
※追記 あるバグが解決されて
https://github.com/zendframework/zf2/commit/4fd2d42e58245f03d1909306a8ef8858974611b1
手順として、

  1. フォームにエレメントを追加する
  2. getInputFilter()で初期化する
  3. 変更したいエレメントの先頭に設定変更したNotEmptyバリデータを入れる

これで大丈夫そうです。

素直にElementを継承して書く

上記した問題は、プリセットなエレメントをそのまま使い、設定だけで処理をしようとしたときにNotEmptyバリデータのカスタムメッセージを設定したいという限られたときにだけ発生するわけですが、ライブラリに手を入れなくても、個別のエレメントクラスを継承して書き、getInputSpecification()で、requiredにしたいエレメントについては、NotEmptyバリデータを先頭に書いておくという方法が素直かもしれません。
http://framework.zend.com/manual/2.0/en/modules/zend.form.elements.html

Apache CouchDBとCouchbase Server2.0は別のプロダクトへ

先日CouchDBPHPについて下記のようなメモを残しました。
http://d.hatena.ne.jp/noopable/20111013/1318490214

Couchbaseは、CouchDBの商用ディストリというイメージを持っていたのですが、Apache CouchDBからDamien Katz氏が離脱し、今後、Couchbase ServerとApache CouchDBの互換性は担保されないという流れのようです。
CouchOneとMembaseが合併したときも驚きましたが、今回は、実質的にCouchbaseが独立ということで、もう少しインパクトが大きいです。
http://www.infoq.com/jp/news/2012/01/Katz-CouchDB-Couchbase-Server
原文は翻訳記事冒頭にあります。
Couchbaseにはdocomoも出資しており、mobile用Couchbaseもあり、モバイル、PCを問わず可搬性のいいポータブルでオフラインに強いドキュメント/アプリ環境としてブレイクする可能性もあると思いますが、うーん、どうでしょう・・・
http://www.couchbase.com/press-releases/couchbase-and-docomo-investment

Zend Framework 2.0 beta2でquick startしてみた。

beta2でskeletonアプリが出てきました。早速、skeletonのquick startを試してみました。
いずれは、コマンドで基本構造やモジュール・コントローラーを作成できるようになるだろうと思いますが、とりあえずskeletonだけでもありがたいです。

なるべく依存するものが少なくなるようにベタにやってみました。

仮想環境の作成

vmware playerで仮想マシンを作成、今回はCentOS5.7としました。
phpのインストール
yum install php53 php53-mbstring php53-mysql php53-pdo php53-gd php53-mcrypt php53-xml

apacheの調整

個人的にテストはUserDirでやることにしているので、UserDirを有効にしていつものrewriteを設定。
Zend Framework標準のものをUserDir対応にして設置しました。
http://framework.zend.com/manual/ja/project-structure.rewrite.html

Skeletonアプリのダウンロード

http://packages.zendframework.com/docs/latest/manual/en/zend.mvc.quick-start.html
こちらの指示に従って進めていきますが、とりあえずダウンロードし、自分のhome直下に解凍、4つのフォルダがありますが、public以下のファイルをpublic_htmlに移しました。(Apacheのデフォルトに合わせた)

ZF2のダウンロード

http://packages.zendframework.com/
こちらのページに従って入手してSkeletonアプリのvendor/ZendFramework以下にlibraryをコピー
結果、vendor/ZendFramework/library/Zend/EventManager てな感じになります。
さて、localhostにアクセスしてみます。


ちゃんちゃん、とりあえず出ました。簡単ですね。

quickstartしてみる

それでは、Zend\Mvcのquickstartの指示に従って試してみます。
http://packages.zendframework.com/docs/latest/manual/en/zend.mvc.quick-start.html
SkeletonにはApplicationというモジュールが含まれ、そこには基本的なviewのrenderとlayout用スクリプト、エラーページ用のビューがあるようです。
モジュール用のSkeletonも用意されています。
• Zip: https://github.com/zendframework/ZendSkeletonModule/zipball/master
• Tarball: https://github.com/zendframework/ZendSkeletonModule/tarball/master

これをダウンロードして指示に従ってrename等していきます。
まず、ダウンロードしたSkeletonモジュールを解凍して先ほど作ったSkeletonアプリのmodule直下に配置して、ディレクトリ名を好きなモジュール名に変更します。
モジュール名の注意としては、設定ファイル上では、CamelCaseをlowercased,dash-separatedにします。FoodFighterならfood-fighterになります。
autoload_classmap.phpをreturn array();にしろと書いてありますがこれは影響しません。
config/module.config.php
については、

<?php
return array(
    'routes' => array(
    ),
    'di' => array(
        'instance' => array(
            'alias' => array(
            ),
            'Zend\View\TemplatePathStack' => array('parameters' => array(
                'paths'  => array(
                    '<module-name>' => __DIR__ . '/../views',
                ),
            )),
        ),
    ),
);

このようにします。部分を先ほど変更したモジュール名に変えます。viewスクリプト(テンプレート)の配置場所を変えたい場合は、ここで設定しますが、通常はこのままで。
設定ファイルとして、私はIniファイルが好きなのですが、ここではPHPファイルを使っています。
この部分は、Module.phpのgetConfigメソッドを好きに書けば変更できます。
配列のdi部分がDIによるインスタンス管理用の設定ですね。ここで設定した名前でどこからでもインスタンスを呼ぶ事ができるようです。
このaliasを見てうれしくなりました。ソフトウエアの品質を向上していくと、コントローラーに具象コードをガツガツ書く開発は終了して、コントローラー機能も抽象化されていく流れだと思うのですが、このaliasはまさにそれです。同じコントローラークラスに対して、パラメーターの与え方のみで別のコントローラーとして振舞わせることができます。

特定のモデル名が現れないので、同じ挙動のコントローラは、モデルが違ってもコードがまったく同じ実装になりますね。わりとこんなふうに、モデルのクラス名すらも隠蔽してMとVCの間の密結合を避けるようにしてる箇所が多く現れます。

http://d.hatena.ne.jp/tanakahisateru/20120105/1325759589

こちらのYiiに関する記事にあるように、コントローラーのクラスそのものは、どのような挙動をするかという性質だけを実装してあればよく、対応するモデル名やパラメーターの与え方については、DI側から制御してやればよいわけです。それには、aliasを張りたいわけですね。はい。是非に。( 私はこれまで、dispatcherを自作してaliasしていたんですよ>< )
たとえば、ZF2側に用意されているRestfulControllerは典型的です。RESTによるリクエストの裁き方は十分に設計されているので、RESTリクエストを受け付けるコントローラーを継承で量産する必要はなく、それぞれのリソース毎にaliasを張るだけでよいわけです。
コントローラーにモデル名直書きとか画面毎に1コントローラーなんてやめようって現場で言っても、もう大丈夫。

さて、話が横道にそれてしまいましたが、元に戻します。

新しいコントローラーを作る

Moduleの基本構造としては、モジュール内のsrc以下がautoloadの対象になりますので、src//Controller以下にコントローラークラスファイルを配置します。
ここでは、HelloController.phpとします。

<?php
namespace <module name>\Controller;

use Zend\Mvc\Controller\ActionController;

class HelloController extends ActionController
{
    public function worldAction()
    {
        $message = $this->getRequest()->query()->get('message', 'foo');
        return array('message' => $message);
    }
}

ViewScriptを書きます。

ViewScriptの配置は、先ほど指定した場所以下に、コントローラー名をlowercase,dash-separatedに直してディレクトリを作成します。
quick-startドキュメントでは、モジュール名-helloにしろとか書いてありますが、要するにaliasとして呼びだすコントローラー名のことなので、そこはこだわらなくてもいいです。
とにかく、コントローラーのalias名を基準にやるとよいです。
そのディレクトリにworld.phtmlを作成します。worldはアクション名で、標準のActionControllerを継承している場合には、worldActionに対応します。

<h1>Greetings!</h1>

<p>You said "$foo".</p>

このように書かれていますが、これはさすがに動きません。Controllerでreturnする配列の添字がmessageですし、デフォルトのrendererはPHPで書きますので、正しくは、

<h1>Greetings!</h1>

<p>You said <?php echo $message;?></p>

となります。echoの後の部分、スクリプト上ではエスケープしてませんが、View側でエスケープしてくれます。
そういえば、ZF1だと、$this->messageじゃなかったっけ?ですが、ここでは、$messageでよいみたいです。

routeを作成する。

多分、デフォルトのルートでもルーティングできると思いますが、ここでは、コントローラーにaliasを使うサンプルも兼ねているようなので、routeを書きます。
ここで、defaultsはルーティングされた際のデフォルトの変数値を指定しますので、controllerのところは、↑で使ったalias名を指定します。
先ほどの、configs/module.config.phpに追記します。

    'routes' => array(
        '<module name>-hello-world' => array(
            'type'    => 'Zend\Mvc\Router\Http\Literal',
            'options' => array(
                'route' => '/hello/world',
                'defaults' => array(
                    'controller' => '<module name>-hello',
                    'action'     => 'world',
                ),
            ),
        ),
    ),

ドキュメントをコピペすると、Zend\Mvc\Router\Http\Literalを囲う文字がバッククォートになっているので要注意です。バッククォートでもエラーにならない場合もあるんでしょうか。私は、ちょっとハマったのですが、おかげでエラー処理のフローもチェックできるので、そのままお試しするのも悪くないですね。
また、コントローラー名が'-hello'となっていますが、この部分はDIで指定するエイリアス名を指定します。

DIでエイリアスを張ります。

大事なことなのでもっかい書いときます。↓で'-hello'となっている部分はエイリアス名にしてください。

<?php
return array(
    // ... routes configuration ...
    'di' => array(
        'instance' => array(
            'alias' => array(
                '<module name>-hello' => '<module namespace>\Controller\HelloController',
            ),
            'Zend\View\TemplatePathStack' => array('parameters' => array(
                'paths'  => array(
                    '<module-name>' => __DIR__ . '/../views',
                ),
            )),
        ),
    ),
);

いいですね。

アプリケーションにモジュールの存在を教えます。

config/application.config.php

<?php
return array(
    'modules' => array(
        'Application',
        '<module namespace>',
    ),
    'module_listener_options' => array( 
        'module_paths' => array(
            './module',
            './vendor',
        ),
    ),
);

はい。これで一応おしまいなので、hello/worldおよびhello/world?message=barへアクセスします。

意図した通りに表示されていればquickstartは完成です。
ほっ。

このSkeltonにしろquickstartにしろ、Zend\Mvcを理解するために作られているものと思いますので、ツールが整備されればもっと楽にできるだろうと思います。

これで、"Zend Framework 日めくり Calendar 2012" http://atnd.org/events/23579 6日目、
Zend Framework2 beta2 を触ってみる。 (featuring EventManager) とさせていただきます。

急遽5日目を代わっていただいた@sasezakiさんありがとうございました。

最後に一言。Zend\Mailが新しくなるようですが、日本語が通るか誰か試してたら教えてください!!

※注)この記事は正式リリースではないbeta版に関するものです。正式版では仕様が変わっているかもしれないのでご注意ください。

「Zend Framework2 beta2 を触ってみる。 (featuring EventManager)」

この記事は"Zend Framework 日めくり Calendar 2012"の「Zend Framework2 beta2 を触ってみる。 (featuring EventManager)」として書きました。

Zend Framework 2.0もbeta2がリリースされ、近くRCそして今春には正式リリースになるそうです。
個人的な印象ですが、ZF1で懸念していた多くの設計上の問題が、ZF2では改善されており、新しいfeatureを追加して別のフレームワークになるのではなく、フレームワークの品質向上に向けた一貫した改善プロセスを踏めているようです。
今日は、ZF1からZF2への変更点の中で、MVCレイヤーにおける変更点とその鍵になるEventManagerに焦点を当て、ZF2の理解に役立つドキュメントやその他リソース、最後にSkeletonアプリを用いたquick startのログをご紹介します。
quick startログへのリンク

ZF1からZF2へ

Zend Frameworkでは、アプリケーションの構造を自由に構成できましたが、推奨のアプリケーション構成もあります。その部分でどのような変化があったかを確認します。
まず、ZF1ですが、FrontControllerパターンでした。FrontControllerから各コントローラーへdispatchループし、モジュール別MVCで具体的な処理を行います。
http://framework.zend.com/manual/ja/learning.quickstart.intro.html
アプリケーションの基本的な処理フローをURL解析、処理、表示という基本的な流れとすると、ZF1ではfrontControllerのdispatchメソッド内で処理フローを決定するテンプレートメソッドパターンと言ってもいいかもしれません。
基本的なフロー(実際Webアプリの大半はこのパターンですが)を踏んでいる分には全く問題ないのですが、実行フローに影響させようとすると、dispatchメソッドそのものを変更せざるをえません。たとえば、routing直後にキャッシュからレスポンスを返すには、dispatchメソッドの変更もしくはオーバーライドが必要でした。
アプリケーションの品質を維持したまま多様性を実現するのに、ZF1.8で追加されたZend_ApplicationによるDIは有効です。しかし、FrontControllerパターンは維持されたため、品質とパフォーマンスのバランスでみるとパフォーマンスが犠牲になったような印象を受けます。
ZF2ではFrontControllerパターンではなく、DIとEventループがアプリケーション構成の基本になります。いずれの方法も言葉単独では違いがわからないのでコードを追いながら、ZF2のEventManagerの振舞いについてみていきたいと思います。

ZF2 MVCの基本的アプリケーション構造とBootstrap

ZF2での基本的な起動スクリプトとしては下記のようなものになります。
http://packages.zendframework.com/docs/latest/manual/en/zend.mvc.html#zend.mvc.intro.bootstrapping-an-application

<?php
// Assuming $config is the merged config from all modules
$bootstrap   = new Bootstrap($config);
$application = new Application();
$bootstrap->bootstrap($application);
$application->run()->send();

ZF2のApplicationクラスはZend\Mvc\AppContextインターフェースを実装したコンテキストとして振舞います。ZF1でFrontController::dispatchで行われてた基本的な処理フローはZF2ではZend\Mvc\Applicationクラスのrunメソッドに集約され、EventManagerが使われます。
自分自身をデフォルトのEventListnerとしてイベントループからrouteメソッドやdispatchメソッド呼ばせています。Applicationの基本的振舞いを変えたければ、Applicationクラス相当のものを作成すればよく、コンテキストとして適切に実装できます。たとえば、社内フレームワークのリプレースを検討している場合や既存のCMS等のクローンを作成するときもそれらのアプリケーションが持っている基本的な処理フローやイベント発生をApplicationクラスとして実装すればよいことになります。
Bootstrapクラスは設定を読み込んで初期設定やインスタンス管理を行います。Applicationコンテキストを作成したら、そのコンテキストに必要な初動を行い(Bootstrap::bootstrap)、コンテキストでアプリケーションを実行します。

<?php
    /**
     * Run the application
     * 
     * @triggers route(MvcEvent)
     *           Routes the request, and sets the RouteMatch object in the event.
     * @triggers dispatch(MvcEvent)
     *           Dispatches a request, using the discovered RouteMatch and 
     *           provided request.
     * @triggers dispatch.error(MvcEvent)
     *           On errors (controller not found, action not supported, etc.), 
     *           populates the event with information about the error type, 
     *           discovered controller, and controller class (if known). 
     *           Typically, a handler should return a populated Response object
     *           that can be returned immediately.
     * @return SendableResponse
     */
    public function run()
    {
        $events = $this->events(); //EventManager instance
        $event  = new MvcEvent();  //implement EventDescription 
        $event->setTarget($this);
        $event->setRequest($this->getRequest())
              ->setRouter($this->getRouter());

        $result = $events->trigger('route', $event, function ($r) {
            return ($r instanceof Response);
        });
        if ($result->stopped()) {
            $response = $result->last();
            return $response;
        }

        $result = $events->trigger('dispatch', $event, function ($r) {
            return ($r instanceof Response);
        });

        $response = $result->last();
        if (!$response instanceof Response) {
            $response = $this->getResponse();
            $event->setResponse($response);
        }

        $events->trigger('finish', $event);

        $response = $response;
        return $response;
    }

EventManager::triggerを見ると数パターンの呼びだし方がありますが、この部分が、EventManagerの基本パターンのうちの一つになります。ここでは、第1引数はイベント名、第2引数はイベントの対象に関する情報を格納するEventDescriptionインスタンス、第3引数はイベントを終了する条件を返すコールバックになります。
Applicationコンテキストでpriority=1でコールバックされるrouteメソッドでは、正常終了するとZend\Mvc\Router\RouteMatchでルート情報を返しますが、routing中にキャッシュを特定出来た場合はここでResponseを返すと、コールバックがtrueとなり、stoppedフラグを立ててイベントループを直ちに終了します。これにより、余分な処理をせずにスマートにキャッシュコンテンツを返すことができます。ZF1に比べるとかなりクールですね。
routing中にエラーが発生すると、dispatch.errorイベントを発生させ、こちらも直ちにエラーコントローラー等でエラー処理することになります。

<?php
        $result = $events->trigger('route', $event, function ($r) {
            return ($r instanceof Response);
        });
        if ($result->stopped()) {
            $response = $result->last();
            return $response;
        }

SPLオブザーバーパターンとEventManager

イベントといえば、オブザーバーパターンも有名ですが、我らがSPLにもSplObserverという、まんまオブザーバーパターン用インターフェースがあります。なぜEventManagerはSPLを使わなかったのか。
http://d.hatena.ne.jp/noopable/20090524/1243120866
こちらでも書きましたが、オブザーバーパターンを素直に実装すると、イベントを監視される側のソースが必要以上に煩雑になってしまうことがあります。SplSubjectの機能部分を監視される側から分離して仲介クラスに任せれば煩雑さは消えるかもしれません。しかしそれだけではなく、Zend\EventManagerはSplインターフェースとは本質的に意図が異なる実装になっているんですね。
処理を中断するスマートかつ汎用的な方法を用意したり、イベントの対象を管理しやすい方法を用意してくれており、アプリケーション全体で統一できるメリットは大きいです。
イベントを処理する他のパターンとZend\EventManagerのコンセプトの違いについては、こちらのZF Leadの方のスライドに詳しく出ているのでもしよかったら見てみてください。
http://www.slideshare.net/weierophinney/zend-framework-20-patterns-tutorial/148

traitとEventManager

EventManagerをtraitで使う例が出ています。
http://www.slideshare.net/weierophinney/zend-framework-20-patterns-tutorial/201
これ、いいですね。EventManagerを使うシチュエーションとしては、バリデーターやフィルター、アクセス制御を適用するケースも考えられますが、たとえばアクセス制御用EventManagerを作成してモデルやコントローラーなどにtraitして使えればうれしいなぁ。そういう展望が立つと、traitが使えない環境でもそれを意識した管理ポリシーが立ちますね。

PHPとCouchDB

ドキュメント志向データベースだとMongoDBが盛り上がっていますが、CouchDBもまだまだ要チェックだと思っています。PHPと共に使う場合、MongoDBへはPHPの標準ライブラリ経由でいいと思いますが、CouchDBにはRESTでアクセスします。この点、余計なHttp接続コストを払って・・云々といった話はあります。既存の3層構造に当てはめればそうですが、最近のクライアント重視な考え方では必ずしもロスにはならない、むしろメリットの方が大きいケースもあると思います。RDBライクなクエリが使えるMongoDBは確かに現在のアーキテクチャのまま永続化層として使用しても違和感がありません。しかしフロントエンドでのパフォーマンス、リソース設計を考えるとCouchDBの特徴を生かしたシステム設計には捨てがたい魅力があります。それについてはまた後日ログを残そうと思いますが、今日はとりあえずPHPからCouchDBに接続するには、です。

CouchDBを触る

とりあえず、一通りはCouchDBでアプリっぽい物を作ってみた方がいいと思っています。PHPからいきなり繋ごうとすると既存のデータ層とのギャップばかりに目が行ってしまって、CouchDBの世界が見えないからです。とくに、MongoDBならRDBライクなクエリが使えますが、CouchDBではMap Reduceでviewを作るというのが基本になります。私はもともとRDBに動的なクエリを打つ趣味はなく、検索はluceneでいいよねと思っているので全く苦になりませんでした。Javascriptflexなどからグリッド用のデータソースを要求する程度ならviewに基本的な処理クエリが用意されているので複雑な結合を要する場合(てかそんなことはMongoでもやらないと思いますが)を除けば十分に対応できます。
私は最初は付属?というか公開されているサンプルブログアプリを触ってみて、既存のjsベースの自前ライブラリから繋げて遊んでみました。なかなかおもしろいです。(具体的には内容がクソなのでご容赦くださいw)

基本的なリソース
ソーシャルとかデータマイニングとか解析とか

PHPからCouchDBに接続する

PHPCouchDBの連携には、HTTPサーバー -> PHP をベースとしてアプリを構築し、永続化層としてCouchDBを置き、PHPから接続するというパターンと、CouchDBをフロントエンドにして、一部の処理を外部プロセスとしてPHPに接続するという逆のパターンがあります。通常は前者の利用法が紹介されることが多いです。
CouchDBをフロントにしてPHPに接続するには、FastCGIPHPを用意して、CouchDB側でvirtualhostと_rewriteを設定する方法が使えることは確認しました。高速なviewをCouchDBで提供し、既存のアプリケーションに接続したいときはPHPに繋ぐというのもありかな。
とりあえず、ここではPHPからCouchDBに接続するには、です。
CouchDBのwikiにサンプルコードが出ています。これを参考にしてもいいですし、一部手を抜くなら、Zend_Http_ClientとZend_Jsonを使うとスッキリするかもしれません。手作業を省略して↓にリストアップしたライブラリのどれかを使うというほうが手っとり早いかもしれません。基本的にはRESTに接続して帰って来たJSONを使う、JSONでリクエストを出すといったところですが、エラー処理やリクエストの準備などで違いが出るかどうか。やってみると、ZFで新しくリリースされたZend_Config_Jsonと合わせて、コンフィグサーバーとしてのみ使うというのも有りだと思います。高度に抽象化されたアプリケーションを作っていれば、ZFアプリをプラットフォームやサービスとして提供とかも可能ですね。

リンクなど

ここでリストアップしたように、CouchDBPHPから接続するライブラリは意外と充実しています。
既存のPHP製Frameworkとの連携を考えると、Doctrine-CouchDBもよさそうですし、Sag for CouchDB もドキュメントがしっかりしているので最初に試すにはよいかもしれません。
私が最初に試したのはZend Frameworkでおなじみの方が書いているこれですが、

ZF1と連動させてシンプルに試したいときはいいかもしれません。

ZF2についてのちょいメモ

"Zend Framework 2.0"はどうなんでしょうか。
サラッとリストアップしてみようと思います。
その他、何かいいリソースがありましたら、教えていただけると助かります!

最新情報の入手

Zend Frameworkの開発ブログがあります。私はたまに気になった時に見ていますが、普段はリーダーに埋没しています。
http://framework.zend.com/zf2/blog
このブログの右メニューにあるリンクがいい感じです。

最新ソースは?

最新ソースは、zend.comのgitにあります。
http://git.zendframework.com/
導入方法は下記のドキュメントが参考になります。
http://framework.zend.com/wiki/display/ZFDEV2/Zend+Framework+Git+Guide
ソースだけですべてがわかるわけではないので、各コンポーネントのステータスを確認しておきます。
http://framework.zend.com/wiki/display/ZFDEV2/Development+Status+Page
このリストに出ていないものは、コミットログが整理されているのでこちらも確認しておきます。
http://framework.zend.com/zf2/status

githubにミラーがありますが、遅れている気がするのですがどうなんでしょう?githubだけwatchしててがっかりしないように!

wikiとか

wikiを見ると、"How to start with Zend Framework 2?"なんてのがあります。ちょっとしたリンク集ってところでしょうか。
http://framework.zend.com/wiki/pages/viewpage.action?pageId=42303506
その中でこれは見ておいて損はないかも
Zend Framework 2.0 Patterns Tutorial
http://www.slideshare.net/weierophinney/zend-framework-20-patterns-tutorial
そのほかには、wikiからリンクされてるwebinarとか参考になるかも。