Skip to content

Commit

Permalink
Merge pull request php-fig#1059 from Crell/psr-14-naming-things
Browse files Browse the repository at this point in the history
Overhaul descriptions for the new naming of things.
  • Loading branch information
WyriHaximus authored Aug 9, 2018
2 parents 8124981 + 4667521 commit eaac6a9
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 39 deletions.
33 changes: 20 additions & 13 deletions proposed/event-dispatcher-meta.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,46 +42,53 @@ On further review, it was determined that Collection was a special case of Objec
* Notification
* Modification

Notification can safely be done asynchronously (including delaying it through a queue) but Modification by nature involve passing data back to the caller and thus must be synchronous. Despite that difference the Working Group determined that the use cases were close enough to be considered in a single PSR. The two different workflows however are represented by two different but related dispatcher interfaces.
Notification can safely be done asynchronously (including delaying it through a queue) but Modification by nature involve passing data back to the caller and thus must be synchronous. Despite that difference the Working Group determined that the use cases were close enough to be considered in a single PSR. The two different workflows however are represented by two different but interfaces, `MessageNotifierInterface` (for Messages) and `TaskProcessorInterface` (for Tasks).

### 4.2 Immutable events

Initially the Working Group wished to define all Events as immutable message objects, similar to PSR-7. However, that proved problematic in the Modification case. In both of those cases Listeners needed a way to return data to the caller. In concept there were three possible avenues:
Initially the Working Group wished to define all Events as immutable message objects, similar to PSR-7. However, that proved problematic in the Processor case. In both of those cases Listeners needed a way to return data to the caller. In concept there were three possible avenues:

* Make the event mutable and modify it in place.
* Require that events be evolvable (immutable but with `with*()` methods like PSR-7 and PSR-13) and that listeners return the event to pass along.
* Make the Event immutable but aggregate and return the return value from each Listener.

However, Stoppable Events (the alternative chain case) also needed to have a channel by which to indicate that further listeners should not be called. That could be done either by:
However, Stoppable Task (the alternative chain case) also needed to have a channel by which to indicate that further listeners should not be called. That could be done either by:

* Modifying the event (`stopPropagation()`)
* Modifying the Task (`stopPropagation()`)
* Returning a sentinel value from the listener (`true` or `false`) to indicate that propagation should terminate.
* Evolving the event to be stopped (`withPropagationStopped()`)
* Evolving the Task to be stopped (`withPropagationStopped()`)

Of those, the first would mandate a mutable event in at least some cases. The second would mandate a mutable event as the return value was already in use. And the third seemed unnecessarily ceremonial and pedantic.
Of those, the first would mandate a mutable Task in at least some cases. The second would mandate a mutable Task as the return value was already in use. And the third seemed unnecessarily ceremonial and pedantic.

Having listeners return evolvable events also posed a challenge. That pattern is not used by any known implementations in PHP or elsewhere. It also relies on the listener to remember to return the object (extra work for the listener author) and to not return some other, new object that might not be fully compatible with later listeners (such as a subclass or superclass of the event).
Having listeners return evolvable tasks also posed a challenge. That pattern is not used by any known implementations in PHP or elsewhere. It also relies on the listener to remember to return the object (extra work for the listener author) and to not return some other, new object that might not be fully compatible with later listeners (such as a subclass or superclass of the event).

Immutable events also rely on the event definer to respect the admonition to be immutable. Events are, by nature, very loosely designed and the potential for implementers to ignore that part of the spec, even inadvertently, is high.

That left two possible options:

* Allow events to be mutable.
* Allow tasks to be mutable.
* Require, but be unable to enforce, immutable objects with a high-ceremony interface, more work for listener authors, and a higher potential for breakage that may not be detectable at compile time.

Given those options the Working Group felt mutable events was the safer alternative.
Given those options the Working Group felt mutable tasks were the safer alternative.

As the Notification use case would technically allow for immutable events to be viable, however, the specification defines that those events SHOULD be immutable, or at least treated as such, and dispatcher implementers are welcome to take steps to enforce that.
The Message Notification use case, however, has no need for return communication so those issues are moot. Those objects therefore MUST be treated as immutable, regardless of what the object's methods are.

### 4.3 Listener registration

Experimentation during development of the specification determined that there were a wide range of viable, legitimate means by which an event dispatcher could be informed of a listener. A listener could be registered explicitly; it could be the registered explicitly based on reflection of its signature; it could be registered with a numeric priority order; it could be registered using a before/after mechanism to control ordering more precisely; it could be registered from a service container; it could use a pre-compile step to generate code; it could be based on method names on objects in the event itself.
Experimentation during development of the specification determined that there were a wide range of viable, legitimate means by which a Notifier or Processor could be informed of a listener.

* A listener could be registered explicitly; it could be the registered explicitly based on reflection of its signature;
* it could be registered with a numeric priority order;
* it could be registered using a before/after mechanism to control ordering more precisely;
* it could be registered from a service container;
* it could use a pre-compile step to generate code;
* it could be based on method names on objects in the event itself.

These and other mechanisms all exist in the wild today in PHP, all are valid use cases worth supporting, and few if any can be conveniently represented as a special case of another. That is, standardizing one way, or even a small set of ways, to inform the system of a listener turned out to be impractical if not impossible without cutting off many use cases that should be supported.

The Working Group therefore chose to encapsulate the registration of listeners behind the `ListenerProviderInterface`. A provider object may have an explicit registration mechanism available, or multiple such mechanisms, or none. It could also be generated code produced by some compile step. That is up to the implementer. However, that also splits the responsibility of managing the process of dispatching an event from the process of mapping an event to listeners. That way different event dispatcher implementations may be mixed-and-matched with different provider mechanisms as needed.
The Working Group therefore chose to encapsulate the registration of listeners behind the `ListenerProviderInterface`. A provider object may have an explicit registration mechanism available, or multiple such mechanisms, or none. It could also be generated code produced by some compile step. That is up to the implementer. However, that also splits the responsibility of managing the process of dispatching an event from the process of mapping an event to listeners. That way different implementations may be mixed-and-matched with different provider mechanisms as needed.

While combining the dispatcher and provider into a single object is a valid and permissible degenerate case, it is NOT RECOMMENDED as it reduces the flexibility of system integrators. Instead, the dispatcher should compose the provider as a dependent object.
While combining the Notifier, Processor, and Provider into a single object is a valid and permissible degenerate case, it is NOT RECOMMENDED as it reduces the flexibility of system integrators. Instead, the provider should be composed as a dependent object.

## 5. People

Expand Down
Loading

0 comments on commit eaac6a9

Please sign in to comment.