目次

PSR-14: Event Dispatcher

y2sunlight 2020-06-23

本章は、若干の補足を加筆してはいるものの単にPSRのサイトを日本語に翻訳したものに過ぎません。英語が堪能な方は原文をご参照下さい。翻訳に当たっては、基本的に機械翻訳を使い、理解できない部分は独断で意訳しております。拙い訳では御座いますが恥を忍んで投稿しておりますので、ご指摘など御座いましたらコメントを頂ければ幸いです。

関連記事


PSR-14: イベントディスパッチャー

原文より翻訳 PSR-14: Event Dispatcher 2020-07-21 現在

イベントディスパッチは、よくテストされた一般的なメカニズムであり、開発者はロジックをアプリケーションに簡単かつ一貫して注入できます。

このPSRの目的は、イベントベースの拡張とコラボレーションのための共通のメカニズムを確立して、さまざまなアプリケーションとフレームワークとの間でライブラリとコンポーネントをより自由に再利用できるようにすることです。

このドキュメントのキーワード MUST , MUST NOT , REQUIRED , SHALL , SHALL NOT , SHOULD , SHOULD NOT , RECOMMENDED , MAY 及び OPTIONAL は、 RFC 2119で説明されているように解釈して下さい。

RFC 2119の説明
MUST, REQUIRED, SHALL — 絶対必要
MUST NOT, SHALL NOT — 絶対禁止
SHOULD, RECOMMENDED — 推奨(但し、無視できる特定の正当な理由が存在するかもしれない)
SHOULD NOT — 推奨できない(但し、許可できる特定の正当な理由が存在するかもしれない)
MAY, OPTIONAL — オプション


目標

イベントをディスパッチし処理するための共通のインターフェースがあると、開発者は、多くのフレームワークや他のライブラリと共通の方法で相互作用できるライブラリを作成できます。

いくつかの例:


定義


イベント

イベントは、エミッターとその適切なリスナーとの通信の単位として機能するオブジェクトです。

エミッターに戻りの情報を提供するリスナーのユースケース呼び出しが必要な場合、イベントオブジェクトはで変更可能であることがあります( MAY )。ただし、そのような双方向通信が必要ない場合は、イベントを不変として定義することをお勧めします( RECOMMENDED )。つまり、イベントには変更可能なメソッドを含まないように定義します。

実装者は、同じオブジェクトが全てのリスナーに渡されることを想定する必要があります( MUST )。

イベントオブジェクトは可逆的なシリアル化と逆シリアル化をサポートすることを推奨されます( RECOMMENDED )。しかし、それは必須ではありません( NOT REQUIRED )。即ち、$event == unserialize(serialize($event)) は true を保つべきです( SHOULD )。 オブジェクトは、PHPの Serializable インターフェース、__sleep() または __wakeup() マジックメソッド、または必要に応じて同様の言語機能を活用できます( MAY )。


停止可能なイベント

停止可能なイベントは、イベントの特殊なケースで、リスナーがさらに呼び出されないようにする追加の方法を含んでいます。これは、StoppableEventInterface を実装することによって示すことができます。

StoppableEventInterfaceを実装するイベントは、それが表すどんなイベントが完了したときでも isPropagationStopped() から true を返す必要があります( MUST )。それがいつであるかを決定するのは、クラスの実装者次第です。例えば、PSR-7 RequestInterface オブジェクトを対応する ResponseInterface オブジェクトと照合するように要求しているイベントは、リスナーが呼び出す setResponse(ResponseInterface $res) メソッドを持つことができます。これにより isPropagationStopped()trueを返すようにできます。


リスナー

リスナーは、任意のPHP callableです。リスナーは、パラメーターを1つだけ持つ必要があります( MUST )。それは、応答すべきイベントです。リスナーは、そのユースケースに特に関連するように、そのパラメーターにタイプヒントを示すべきです( SHOULD )。つまり、リスナーはインターフェースに対するタイプヒントを示す場合があり( MAY )、それはそのインターフェースを実装するすべてのイベントタイプまたはそのインターフェースの特定の実装と互換性があることを示します。

リスナーの戻り値は void であるべきで( SHOULD )、明示的に戻すタイプヒントがあるべきです( SHOULD )。ディスパッチャーは、リスナーからの戻り値を無視する必要があります( MUST )。

リスナーは他のコードにアクションを委任することができます( MAY )。これには、薄いラッパーであるリスナーが含まれていて、それは実際のビジネスロジックを実行するオブジェクトを包んでいます。

リスナーは、cron、キューサーバー、または同様の手法を使用し、二次プロセスによるて後処理のために、イベントから情報をキューに追加する場合があります( MAY )。それを行うために、イベントオブジェクト自体をシリアル化することができます( MAY )。ただし、すべてのイベントオブジェクトが安全にシリアル化できるとは限らないことに注意する必要があります。二次プロセスは、イベントオブジェクトに加えた変更が他のリスナーに伝播しないこと( NOT )を前提とする必要があります( MUST )。

上記の原文
A Listener MAY enqueue information from the Event for later processing by a secondary process, using cron, a queue server, or similar techniques. It MAY serialize the Event object itself to do so; however, care should be taken that not all Event objects may be safely serializable. A secondary process MUST assume that any changes it makes to an Event object will NOT propagate to other Listeners.


ディスパッチャー

ディスパッチャーは、EventDispatcherInterface を実装するサービスオブジェクトです。それは、ディスパッチされるイベントに対するリスナープロバイダーからリスナーを取得し、そのイベントで各リスナーを呼び出す責任があります。

ディスパッチャーは:

停止可能なイベントが渡された場合、ディスパッチャーは:

ディスパッチャーは、リスナープロバイダーから返される全てのリスナーがタイプセーフであると仮定すべきです( SHOULD )。つまり、ディスパッチャーは、$listener($event) を呼び出しても TypeError が生成されないことを仮定すべきです( SHOULD )。


エラー処理

リスナーによってスローされた例外またはエラーは、それ以降のリスナーの実行をブロックする必要があります( MUST )。リスナーによってスローされた例外またはエラーは、エミッターにまで遡って伝播することが許可されていなければなりません( MUST )。

ディスパッチャーは、スローされたオブジェクトをキャッチしてログに記録したり、追加のアクションを実行したりできますが( MAY )、その後は、元の throwable を再スローする必要があります( MUST )。


リスナープロバイダー

リスナープロバイダーは、与えられたイベントに対してどのリスナーが関連し、どのリスナーを呼び出すべきかを決定することに責任があるサービスオブジェクトです。それは、関連するリスナーと、選択した手段によるそれらリスナーを返すべき順序、の両方を決定するかもしれません。それには以下の場合が含まれています( MAY ):

上記の任意の組み合わせ、または他のメカニズムが、必要に応じて使用できます( MAY )。

リスナープロバイダーは、イベントのクラス名を使用して、そのイベントと他のイベントを区別すべきです( SHOULD )。それらはまた、必要に応じて、イベントに関するその他の情報を考慮することもできます( MAY )。

リスナープロバイダーは、リスナーの適用性を決定するときに、親のタイプをイベント自体のタイプと同じように取り扱う必要があります( MUST )。次の場合:

class A {}
 
class B extends A {}
 
$b = new B();
 
function listener(A $event): void {};

リスナープロバイダーは、タイプ互換であるため、他の基準によって妨げられない限り、listener()$b の適切なリスナーとして取り扱う必要があります( MUST )。


オブジェクト構成

ディスパッチャーは、関連するリスナーを決定するためにリスナープロバイダーを構成する必要があります( SHOULD )。 リスナープロバイダーをディスパッチャーとは異なるオブジェクトとして実装することをお勧めしますが( RECOMMENDED )、必須ではありません( NOT REQUIRED )。


インターフェース

EventDispatcherInterface.php
namespace Psr\EventDispatcher;
 
/**
 * イベントのディスパッチャを定義します。
 */
interface EventDispatcherInterface
{
    /**
     * 処理するイベントをすべての関連リスナーに提供します。
     *
     * @param object $event
     *   処理するオブジェクト。
     *
     * @return object
     *   渡されたイベント。リスナーによって変更される。
     */
    public function dispatch(object $event);
}
ListenerProviderInterface.php
namespace Psr\EventDispatcher;
 
/**
 * イベントからそのイベントに適用可能なリスナーへのマッパー。
 */
interface ListenerProviderInterface
{
    /**
     * @param object $event
     *   関連するリスナーを返すイベント。
     * @return iterable<callable>
     *   iterable型のcallableオブジェクト(配列、iterator、またはgenerator)。 
     *   個々の callableオブジェクト は、$eventと型互換でなければなりません( MUST )。
     */
    public function getListenersForEvent(object $event) : iterable;
}
StoppableEventInterface.php
namespace Psr\EventDispatcher;
 
/**
 * イベントが操作されたとき、処理が中断される可能性があるイベント。
 *
 * Dispatcherの実装は、各リスナーが呼び出された後にイベントが停止済みとして
 * マークされているかどうかを確認する必要があります( MUST )。もしそうであれば、
 * それ以上リスナーを呼び出さずにすぐに戻る必要があります。
 *
 */
interface StoppableEventInterface
{
    /**
     * 伝播が停止しているか?
     *
     * これは通常、前のリスナーが伝播を停止したかどうかを判断するために
     * Dispatcherによってのみ使用されます。
     *
     * @return bool
     *   イベントが完了し、それ以上リスナーを呼び出さない場合はTrue。
     *   リスナーの呼び出しを続行する場合はFalse。
     */
    public function isPropagationStopped() : bool;
}