====== PSR-14: Event Dispatcher ====== --- //[[http://www.y2sunlight.com|y2sunlight]] 2020-06-23// 本章は、若干の補足を加筆してはいるものの単に[[https://www.php-fig.org/psr/|PSRのサイト]]を日本語に翻訳したものに過ぎません。英語が堪能な方は原文をご参照下さい。翻訳に当たっては、基本的に機械翻訳を使い、理解できない部分は独断で意訳しております。拙い訳では御座いますが恥を忍んで投稿しておりますので、ご指摘など御座いましたらコメントを頂ければ幸いです。 関連記事 * [[psr:top|PSR - PHP標準勧告]] * [[psr:psr1|PSR-1: Basic Coding Standard - 基本コーディング規約]] * [[psr:psr3|PSR-3: Logger Interface - ロガーインターフェイス]] * [[psr:psr4|PSR-4: Autoloading Standard - オートローディング規約]] * [[psr:psr5|PSR-5: PHPDoc Standard(Draft) - PHPDoc規約]] * [[psr:psr6|PSR-6: Caching Interface - キャッシングインターフェイス]] * [[psr:psr7|PSR-7: HTTP Message Interface - HTTPメッセージインターフェイス]] * [[psr:psr11|PSR-11: Container Interface - コンテナインターフェイス]] * [[psr:psr12|PSR-12: Extended Coding Style - 拡張コーディングスタイル]] * [[psr:psr13|PSR-13: Link definition interfaces - リンク定義インターフェース]] * PSR-14: Event Dispatcher - イベントディスパッチャー * [[psr:psr15|PSR-15: HTTP Server Request Handlers - HTTPサーバーリクエストハンドラー]] * [[psr:psr16|PSR-16: Common Interface for Caching Libraries - キャッシングライブラリのための共通インターフェース]] * [[psr:psr17|PSR-17: HTTP Factories - HTTPファクトリー]] * [[psr:psr18|PSR-18: HTTP Client - HTTPクライアント]] * [[psr:psr19|PSR-19: PHPDoc tags(Draft) - PHPDocタグ]] ----- ====== PSR-14: イベントディスパッチャー ====== --- // 原文より翻訳 [[https://www.php-fig.org/psr/psr-14/|PSR-14: Event Dispatcher]] 2020-07-21 現在 // イベントディスパッチは、よくテストされた一般的なメカニズムであり、開発者はロジックをアプリケーションに簡単かつ一貫して注入できます。 このPSRの目的は、イベントベースの拡張とコラボレーションのための共通のメカニズムを確立して、さまざまなアプリケーションとフレームワークとの間でライブラリとコンポーネントをより自由に再利用できるようにすることです。 このドキュメントのキーワード ''MUST'' , ''MUST NOT'' , ''REQUIRED'' , ''SHALL'' , ''SHALL NOT'' , ''SHOULD'' , ''SHOULD NOT'' , ''RECOMMENDED'' , ''MAY'' 及び ''OPTIONAL'' は、 [[https://www.ietf.org/rfc/rfc2119.txt|RFC 2119]]で説明されているように解釈して下さい。 > **RFC 2119の説明** > ''MUST'', ''REQUIRED'', ''SHALL'' --- 絶対必要 > ''MUST NOT'', ''SHALL NOT'' --- 絶対禁止 > ''SHOULD'', ''RECOMMENDED'' --- 推奨(但し、無視できる特定の正当な理由が存在するかもしれない) > ''SHOULD NOT'' --- 推奨できない(但し、許可できる特定の正当な理由が存在するかもしれない) > ''MAY'', ''OPTIONAL'' --- オプション \\ ===== 目標 ===== イベントをディスパッチし処理するための共通のインターフェースがあると、開発者は、多くのフレームワークや他のライブラリと共通の方法で相互作用できるライブラリを作成できます。 いくつかの例: * ユーザーに権限がない場合にデータの保存やアクセスを防止するセキュリティフレームワーク * 一般的な全ページキャッシュシステム * どのフレームワークに統合されているかに関係なく、他のライブラリを拡張するライブラリ * アプリケーション内で行われたすべてのアクションを追跡するロギングパッケージ \\ ===== 定義 ===== * **イベント** -- イベントは、エミッターが生成するメッセージです。任意のPHPオブジェクトを使用できます。\\ \\ * **リスナー** -- リスナーは、イベントを渡されることを期待している任意のPHP callableです。同じイベントは異なったリスナーに渡すことができます。リスナーは、必要に応じて他の非同期動作をキューに追加できます( ''MAY'' )。\\ \\ * **エミッター** -- エミッターは、イベントをディスパッチしたい任意のコードです。これは「呼び出しコード(calling code)」とも呼ばれます。これは、特定のデータ構造によって表現されるのではなく、ユースケースを指しています。\\ \\ * **ディスパッチャー** -- ディスパッチャーは、エミッターによってイベントオブジェクトが与えられるサービスオブジェクトです。 ディスパッチャーは、イベントが全ての関連するリスナーに確実に渡されるようにする責任があります。ただし、責任のあるリスナーの決定をリスナープロバイダーに任せる必要があります( ''MUST'' )。\\ \\ * **リスナープロバイダー** -- リスナープロバイダーは、与えられたイベントに関連するリスナーを決定する責任があります。ただし、リスナー自体を呼び出してはいけません( ''MUST NOT'' )。リスナープロバイダーは、いくつかの関連するリスナーを指定できます。 \\ ===== イベント ===== イベントは、エミッターとその適切なリスナーとの通信の単位として機能するオブジェクトです。 エミッターに戻りの情報を提供するリスナーのユースケース呼び出しが必要な場合、イベントオブジェクトはで変更可能であることがあります( ''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'' を実装するサービスオブジェクトです。それは、ディスパッチされるイベントに対するリスナープロバイダーからリスナーを取得し、そのイベントで各リスナーを呼び出す責任があります。 ディスパッチャーは: * ListenerProvider から返された順番で、リスナーを同期的に呼び出さなければなりません( ''MUST'' )。 * リスナーの呼び出し完了後に渡されたものと同じイベントオブジェクトを返さなければなりません( ''MUST'' )。 * すべてのリスナーが実行されるまで、エミッターに戻ってはいけません( ''MUST NOT'' )。 停止可能なイベントが渡された場合、ディスパッチャーは: * そのイベントで各リスナーを呼び出す前に、''isPropagationStopped()'' を呼び出す必要があります( ''MUST'' )。そのメソッドが ''true'' を返す場合、それは直ちにイベントをエミッターに返さなければならず( ''MUST'' )、それ以上のリスナーを呼び出してはいけません( MUST NOT )。これは、''isPropagationStopped()'' から常に ''true'' を返すイベントがディスパッチャーに渡される場合、リスナーが呼び出されないことを意味します。 ディスパッチャーは、リスナープロバイダーから返される全てのリスナーがタイプセーフであると仮定すべきです( ''SHOULD'' )。つまり、ディスパッチャーは、''$listener($event)'' を呼び出しても ''TypeError'' が生成されないことを仮定すべきです( ''SHOULD'' )。 \\ ==== エラー処理 ==== リスナーによってスローされた例外またはエラーは、それ以降のリスナーの実行をブロックする必要があります( ''MUST'' )。リスナーによってスローされた例外またはエラーは、エミッターにまで遡って伝播することが許可されていなければなりません( ''MUST'' )。 ディスパッチャーは、スローされたオブジェクトをキャッチしてログに記録したり、追加のアクションを実行したりできますが( ''MAY'' )、その後は、元の throwable を再スローする必要があります( ''MUST'' )。 \\ ===== リスナープロバイダー ===== リスナープロバイダーは、与えられたイベントに対してどのリスナーが関連し、どのリスナーを呼び出すべきかを決定することに責任があるサービスオブジェクトです。それは、関連するリスナーと、選択した手段によるそれらリスナーを返すべき順序、の両方を決定するかもしれません。それには以下の場合が含まれています( ''MAY'' ): * 実装者がイベントにリスナーを固定された順序で割り当てることができるように、ある種の登録メカニズムを可能にします。\\ \\ * イベントのタイプと実装されたインターフェースに基づいたリフレクションにより、該当するリスナーのリストを導き出します。\\ \\ * 前もって実行時に参照される可能性のあるリスナーの編集済みリストを生成します。\\ \\ * 現在のユーザーが特定の権限を持っている場合に特定のリスナーだけが呼び出されるのように、ある種のアクセス制御を実装します。\\ \\ * エンティティのような、イベントによって参照されるオブジェクトから情報を抽出し、そのオブジェクトで事前定義されたライフサイクルメソッドを呼び出します。\\ \\ * いくつかの任意のロジックを使用して、その責任を1つ以上の他のリスナープロバイダーに委任します。 上記の任意の組み合わせ、または他のメカニズムが、必要に応じて使用できます( ''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'' )。 \\ ===== インターフェース ===== namespace Psr\EventDispatcher; /** * イベントのディスパッチャを定義します。 */ interface EventDispatcherInterface { /** * 処理するイベントをすべての関連リスナーに提供します。 * * @param object $event * 処理するオブジェクト。 * * @return object * 渡されたイベント。リスナーによって変更される。 */ public function dispatch(object $event); } namespace Psr\EventDispatcher; /** * イベントからそのイベントに適用可能なリスナーへのマッパー。 */ interface ListenerProviderInterface { /** * @param object $event * 関連するリスナーを返すイベント。 * @return iterable * iterable型のcallableオブジェクト(配列、iterator、またはgenerator)。 * 個々の callableオブジェクト は、$eventと型互換でなければなりません( MUST )。 */ public function getListenersForEvent(object $event) : iterable; } namespace Psr\EventDispatcher; /** * イベントが操作されたとき、処理が中断される可能性があるイベント。 * * Dispatcherの実装は、各リスナーが呼び出された後にイベントが停止済みとして * マークされているかどうかを確認する必要があります( MUST )。もしそうであれば、 * それ以上リスナーを呼び出さずにすぐに戻る必要があります。 * */ interface StoppableEventInterface { /** * 伝播が停止しているか? * * これは通常、前のリスナーが伝播を停止したかどうかを判断するために * Dispatcherによってのみ使用されます。 * * @return bool * イベントが完了し、それ以上リスナーを呼び出さない場合はTrue。 * リスナーの呼び出しを続行する場合はFalse。 */ public function isPropagationStopped() : bool; } \\