ZF2 EventManager

Matthew Weier O'Phinneyブログの Zend Framework 2のEventManagerに関する記事のわずかに無料の翻訳。
例の記事でZend\EventManagerZend\EventManager何か、その使用方法、PHPでのプログラミングの問題を解決するイベントベースの方法の利点について説明しています。 ZF2の新機能について。
オリジナルと翻訳は、zbeta2.dev4のリリース中に作成されました。.beta1の前に、大きな変更はありませんでした。 それでも、この記事はレビューに使用する必要があります。

用語



イベントとは、いつどのようにトリガーされたか、どのオブジェクトがそれを呼び出したか、渡されたパラメーターなどのデータを含むオブジェクトです。 イベントには名前もあります。これにより、このイベントの名前を参照して、ハンドラーを特定のイベントにバインドできます。

始めましょう


これらすべてを処理するために必要な最小限のもの:

 use Zend\EventManager\EventManager; $events = new EventManager(); $events->attach('do', function($e) { $event = $e->getName(); $params = $e->getParams(); printf( 'Handled event "%s", with parameters %s', $event, json_encode($params) ); }); $params = array('foo' => 'bar', 'baz' => 'bat'); $events->trigger('do', null, $params); 


出力では次のようになります。
 パラメータ "" foo ":" bar "、" baz ":" bat "}でイベント" do "を処理しました 

複雑なことはありません!
注:例では匿名関数を使用していますが、関数名、静的クラスメソッド、またはオブジェクトメソッドを使用できます。

しかし、 $events->trigger()メソッドの2番目の「null」引数は何ですか?

通常、 EventManagerクラス内EventManager使用され、イベントはそのクラスのメソッド内でトリガーされます。 そして、この2番目の引数は「コンテキスト」または「ターゲット」であり、説明した場合、このクラスのインスタンスになります。 これにより、イベントハンドラーがリクエストオブジェクトにアクセスできるようになります。これは、役に立つ/必要な場合があります。

 use Zend\EventManager\EventCollection, Zend\EventManager\EventManager; class Example { protected $events; public function setEventManager(EventCollection $events) { $this->events = $events; } public function events() { if (!$this->events) { $this->setEventManager(new EventManager( array(__CLASS__, get_called_class()) ); } return $this->events; } public function do($foo, $baz) { $params = compact('foo', 'baz'); $this->events()->trigger(__FUNCTION__, $this, $params); } } $example = new Example(); $example->events()->attach('do', function($e) { $event = $e->getName(); $target = get_class($e->getTarget()); // "Example" $params = $e->getParams(); printf( 'Handled event "%s" on target "%s", with parameters %s', $event, $target, json_encode($params) ); }); $example->do('bar', 'bat'); 


この例は、基本的に最初のものと同じことを行います。 主な違いは、 trigger()メソッドの2番目の引数、コンテキスト(このイベントを処理するプロセスを開始したオブジェクト)にコンテキストを渡し、ハンドラーが$e->getTarget()メソッドを介してそれを受け取り、それを(理由内で)実行できることです:))。

次の2つの質問があります。

答えはさらにです。

EventCollection vs EventManager


ZF2が従おうとしている原則の1つは、リスク代替原則です。 この原則の解釈は次のようになります。将来、別のクラスによって再定義する必要がある可能性のあるクラスについては、「ベース」インターフェイスを定義する必要があります。 また、これにより、開発者はこのインターフェイスのメソッドを定義することにより、クラスの異なる実装を使用できます。

したがって、 EventCollectionインターフェースが開発されました。これは、リスナーをイベントに集約し、これらのイベントを開始できるオブジェクトを記述しています。 EventManagerは、 EventManager標準実装です。

StaticEventManager


EventManager実装が提供する1つの側面は、StaticEventCollectionと対話するStaticEventCollectionです。 このクラスを使用すると、ハンドラーを名前付きイベントだけでなく、特定のコンテキストまたはターゲットによってトリガーされたイベントにも添付できます。 EventManager 、イベントを処理するときに、 StaticEventCollectionオブジェクトから(現在のコンテキストにサブスクライブされた)イベントハンドラーをStaticEventCollectionして実行します。

どのように機能しますか?

 use Zend\EventManager\StaticEventManager; $events = StaticEventManager::getInstance(); $events->attach('Example', 'do', function($e) { $event = $e->getName(); $target = get_class($e->getTarget()); // "Example" $params = $e->getParams(); printf( 'Handled event "%s" on target "%s", with parameters %s', $event, $target, json_encode($params) ); }); 


この例は、前の例とほとんど同じです。 唯一の違いは、 attach()メソッドの最初の引数に、ハンドラーをアタッチするコンテキスト-'Example'を渡すことです。 言い換えると、「do」イベントを処理するときに、このイベントが「Example」コンテキストによってトリガーされた場合、ハンドラーを呼び出します。

これは、まさにEventManagerコンストラクターEventManagerが役割を果たす場所です。 このコンストラクターを使用すると、 StaticEventManagerからイベントハンドラーを取得するコンテキストの名前を定義する文字列または文字列の配列を渡すことができます。 コンテキストの配列が渡されると、これらのコンテキストのすべてのイベントハンドラーが実行されます。 EventManager直接接続されたイベントハンドラーは、 StaticEventManager定義されたハンドラーの前に実行されます。

Exampleクラスの定義と最後の2つの例の静的イベントハンドラを組み合わせて、次を追加します。

 $example = new Example(); $example->do('bar', 'bat'); 


出力では次のようになります。
 ターゲット "Example"のイベント "do"を処理し、パラメーター{"foo": "bar"、 "baz": "bat"}を使用 

次に、Exampleクラスを拡張しましょう。

 class SubExample extends Example { } 


EventManagerコンストラクターに渡すパラメーターに注意してください。これは__CLASS__get_called_class()配列です。 つまり、 SubExampleクラスのdo()メソッドを呼び出すと、イベントハンドラーも実行されます。 コンストラクターで 'SubExample'のみを指定した場合、ハンドラーはSubExample::do()でのみ実行され、 SubExample::do()では実行されません。

コンテキストまたは目標として使用される名前は、クラス名である必要はありません。 任意の名前を使用できます。 たとえば、キャッシングまたはロギングを担当するクラスのセットがある場合、コンテキストに「log」および「cache」という名前を付け、クラス名ではなくこれらの名前を使用できます。

Event Managerで静的イベントを処理したくない場合は、 nullパラメーターをsetStaticConnections()メソッドに渡すことができます。

 $events->setStaticConnections(null); 


静的イベントの処理を再接続するには:

 $events->setStaticConnections(StaticEventManager::getInstance()); 


リスナー集合


いくつかのイベントを処理するためにクラス全体に署名する必要があり、この「クラスハンドラ」でいくつかのイベントを処理するためのメソッドを定義します。 これを行うには、「クラスハンドラー」にHandlerAggregateインターフェースを実装します。 このインターフェイスは、 attach(EventCollection $events)detach(EventCollection $events) 2つのメソッドを定義します。

(私自身は翻訳したものを理解していませんでした。以下の例はより明確です)。

 use Zend\EventManager\Event, Zend\EventManager\EventCollection, Zend\EventManager\HandlerAggregate, Zend\Log\Logger; class LogEvents implements HandlerAggregate { protected $handlers = array(); protected $log; public function __construct(Logger $log) { $this->log = $log; } public function attach(EventCollection $events) { $this->handlers[] = $events->attach('do', array($this, 'log')); $this->handlers[] = $events->attach('doSomethingElse', array($this, 'log')); } public function detach(EventCollection $events) { foreach ($this->handlers as $key => $handler) { $events->detach($handler); unset($this->handlers[$key]; } $this->handlers = array(); } public function log(Event $e) { $event = $e->getName(); $params = $e->getParams(); $log->info(sprintf('%s: %s', $event, json_encode($params))); } } 


このようなハンドラーをイベントマネージャーに追加するには、以下を使用します。

 $doLog = new LogEvents($logger); $events->attachAggregate($doLog); 


ハンドラー( LogEvents )が処理するイベントは、対応するクラスメソッドによって処理されます。 これにより、「複雑な」イベントハンドラーを1か所で定義できます(ステートフルハンドラー)。

detach()メソッドに注意してdetach()attach()と同様に、 EventManagerオブジェクトを引数としてEventManager 、イベントマネージャーからすべてのハンドラー(ハンドラーのこの配列- $this->handlers[] )を「切断」します。 これは、 EventManager::attach()がハンドラーを表すオブジェクトを返すために可能です。これは、 LogEvents::attach()メソッドで以前に「添付」したものです。

ハンドラー結果


イベントハンドラーの実行結果を取得する必要がある場合があります。 複数のハンドラーが1つのイベントにサブスクライブできることに注意してください。 また、各ハンドラーの結果が他のハンドラーの結果と競合しないようにしてください。

EventManagerResponseCollectionオブジェクトを返します。 このクラスはSplStackクラスを継承し、すべてのハンドラーの結果にアクセスできます(最後のハンドラーの結果は結果スタックの先頭になります)。

ResponseCollectionには、 SplStackメソッドに加えて、追加のメソッドがあります。

原則として、イベントの処理を開始するとき、ハンドラーの作業の結果にあまり依存しないでください。 さらに、イベントをトリガーするときに、どのイベントハンドラーがこのイベントにサブスクライブするかを常に確認することはできません(おそらくハンドラーがまったくないため、結果が得られない可能性があります)。 ただし、必要な結果がいずれかのハンドラーで既に取得されている場合は、ハンドラーの実行を中断する機会があります。

割り込みイベント処理


ハンドラーの1つがイベントイニシエーターが予期した結果を受け取った場合。 または、ハンドラーが突然何かが間違っていると判断した。 または、何らかの理由で、ハンドラーの1つが後続のハンドラーを実行する必要がないと判断した場合-イベントハンドラーの「スタック」の実行を中断するメカニズムがあります。

これが必要な場合の例は、 EventManager基づいて構築されたキャッシングメカニズムEventManager 。 メソッドの開始時に、「キャッシュ内のデータを検索」イベントをトリガーします。ハンドラーの1つが、キャッシュ内で必要なデータを見つけた場合、残りのハンドラーは中断され、キャッシュから受信したデータを返します。 見つからない場合は、データを生成して「キャッシュに書き込む」イベントを発生させます

EventManagerは、これを実装する2つの方法を提供します。 最初の方法は、実行された各ハンドラーの結果を確認する特別なtriggerUntil()メソッドを使用することです。結果が特定の要件を満たしている場合、後続のハンドラーの実行は中断されます。

例:

 public function someExpensiveCall($criteria1, $criteria2) { $params = compact('criteria1', 'criteria2'); $results = $this->events()->triggerUntil(__FUNCTION__, $this, $params, function ($r) { return ($r instanceof SomeResultClass); }); if ($results->stopped()) { return $results->last(); } // ... do some work ... } 


triggerUntil()メソッドの引数は、最後の追加引数を除いて、 trigger()メソッドの引数に似ています-コールバック関数。各ハンドラーの結果をチェックし、 true返すtrue 、後続のハンドラーの実行が中断されます。

このメソッドに従って、イベントの処理を中断する理由として考えられるのは、最後に実行されたハンドラーの結果が基準を満たすことです。

イベント処理を中断する別の方法は、ハンドラー自体の本体でstopPropagation(true)メソッドを使用することです。 イベントマネージャーが後続のハンドラーの実行を停止させる原因は何ですか。

 $events->attach('do', function ($e) { $e->stopPropagation(); return new SomeResultClass(); }); 


このアプローチでは、プロセッサの最後の結果が基準に対応しているために、イベントの処理が中断されたことを確認できなくなります。

ハンドラー実行順序


ハンドラーの実行順序を指定することもできます。 たとえば、他のハンドラーがいつでもこのイベントの処理を中断できるにもかかわらず、ログに書き込むハンドラーの実行を保証する必要があります。 または、キャッシュを実装する場合:キャッシュ内を検索するハンドラーが他のハンドラーよりも早く実行されました。 反対に、キャッシュに書き込むプロセッサーは、後で実行されます。

EventManager::attach()およびStaticEventManager::attach()にはオプションのpriority引数(デフォルトでは1)があり、これを使用してハンドラーの実行優先度を制御できます。 優先度の高いハンドラーは、優先度の低いハンドラーの前に実行されます。

 $priority = 100; $events->attach('Example', 'do', function($e) { $event = $e->getName(); $target = get_class($e->getTarget()); // "Example" $params = $e->getParams(); printf( 'Handled event "%s" on target "%s", with parameters %s', $event, $target, json_encode($params) ); }, $priority); 


Matthew Weier O'Phinneyは、どうしても必要な場合にのみ優先順位を使用することをお勧めします。 そして、おそらく私は彼に同意します。

すべてをまとめる:シンプルなキャッシングメカニズム


前のセクションでは、イベントを使用してイベントの処理を中断することは、アプリケーションにキャッシュメカニズムを実装する興味深い方法であると書かれていました。 この完全な例を作成しましょう。

まず、キャッシュを使用できるメソッドを定義します。

彼の例では、Matthew Weier O'Phinneyは、イベントの名前として__FUNCTION__をよく使用します。 __FUNCTION__は、イベントをトリガーするマクロを簡単に記述でき、これらの名前の一意性を一意に決定できるためです(特にコンテキストは通常​​クラスによって開始されるため)イベント)。 また、1つのメソッドのフレームワーク内で呼び出されるイベントを分離するには、「do.pre」、「do.post」、「do.error」などの接尾辞を使用します。

また、イベントに渡される$paramsは、メソッドに渡される引数のリストです。 これは、引数がオブジェクトに保存されず、ハンドラーがコンテキストから必要なパラメーターを取得できないためです。 しかし、問題は残ります。キャッシュに書き込むイベントの結果パラメーターに名前を付ける方法は? 例では__RESULT__使用し__RESULT__ 。これは、両側の二重下線が通常システムによって予約されているため便利です。

メソッドは次のようになります。

 public function someExpensiveCall($criteria1, $criteria2) { $params = compact('criteria1', 'criteria1'); $results = $this->events()->triggerUntil(__FUNCTION__ . '.pre', $this, $params, function ($r) { return ($r instanceof SomeResultClass); }); if ($results->stopped()) { return $results->last(); } // ... do some work ... $params['__RESULT__'] = $calculatedResult; $this->events()->trigger(__FUNCTION__ . '.post', $this, $params); return $calculatedResult; } 


次に、キャッシュを操作するイベントハンドラーを定義します。 イベント「someExpensiveCall.pre」および「someExpensiveCall.post」にハンドラーをアタッチする必要があります。 最初のケースでは、データがキャッシュで見つかった場合、それを返します。 後者では、データをキャッシュに保存します。

また、 $cache変数は以前に定義されており、Zend_Cacheオブジェクトに似ていると想定しています。 「someExpensiveCall.pre」ハンドラーの場合、優先度を100に設定して、ハンドラーが他のハンドラーよりも先に実行されるようにし、「someExpensiveCall.post」の場合、他のハンドラーがキャッシュに書き込む前にデータを変更する場合の優先度は100です。

 $events->attach('someExpensiveCall.pre', function($e) use ($cache) { $params = $e->getParams(); $key = md5(json_encode($params)); $hit = $cache->load($key); return $hit; }, 100); $events->attach('someExpensiveCall.post', function($e) use ($cache) { $params = $e->getParams(); $result = $params['__RESULT__']; unset($params['__RESULT__']); $key = md5(json_encode($params)); $cache->save($result, $key); }, -100); 

注: HandlerAggregateを定義し、匿名関数にインポートするのではなく、クラスプロパティに$cacheを格納できます。


もちろん、オブジェクト自体にキャッシュメカニズムを実装し、イベントハンドラーに配置することはできません。 ただし、このアプローチにより、キャッシュハンドラーを他のイベントに接続する機会(他のクラスのキャッシュメカニズムを実装し、キャッシュからフェッチするロジックを保存し、キャッシュを1か所に保存する)、またはこれらのイベントに他のハンドラーをアタッチする(ログに記録するなど)または検証)。 実際、イベントを使用してクラスを設計すると、より柔軟で拡張性の高いものになります。

おわりに


EventManagerは、Zend Frameworkへの新しく強力な追加機能です。 すでに、新しいMVCプロトタイプで使用され、その側面のいくつかの機能を拡張しています。 ZF2のリリース後、イベントモデルは非常に需要があると確信しています。

もちろん、人々が排除するために取り組んでいるいくつかの粗さがあります。

私は自分で追加します-根本的に新しいものは何もありません、そのようなものがZendeに表示されるのは素晴らしいことです-私は間違いなくそれを使用します。
テキストは用語であふれすぎて読みにくいと思います(一部は記事の翻訳経験が少ないため)。
私は批判に対して何もしません。

オリジナル: http : //weierophinney.net/matthew

Source: https://habr.com/ru/post/J131077/


All Articles