Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Provide duck-typed PSR-14 implementation #73

Open
wants to merge 26 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
99c6446
feat: StoppableEventInterface implementation
weierophinney Apr 2, 2019
f1ad8bc
feat: prefer `isPropagationStopped` over `propagationIsStopped`
weierophinney Apr 2, 2019
4fbd54a
feat: proxy to isPropagationStopped
weierophinney Apr 2, 2019
9784e7b
docs: provide comprehensive checklist for PSR-14 adoption
weierophinney Apr 2, 2019
8b89424
docs: Mark StoppableEventInterface as deprecated
weierophinney Apr 3, 2019
ed5fb45
feat: ListenerProvider namespace and interface
weierophinney Apr 3, 2019
092b8e3
feat: Creates PrioritizedListenerProviderInterface
weierophinney Apr 3, 2019
2446145
feat: Creates PrioritizedListenerAttachmentInterface
weierophinney Apr 3, 2019
650a6f6
feat: Creates PrioritizedListenerProvider
weierophinney Apr 3, 2019
c8b7d6a
feat: Creates PrioritizedIdentifierListenerProvider
weierophinney Apr 3, 2019
5569b4e
feat: Creates a PrioritizedAggregateListenerProvider
weierophinney Apr 4, 2019
64ac503
refactor: Have SharedEventManager extend PrioritizedIdentifierListene…
weierophinney Apr 4, 2019
0684663
feat: return listener from attach, attachWildcardListener methods
weierophinney Apr 4, 2019
1093041
feat: adds a ListenerSubscriberInterface
weierophinney Apr 4, 2019
eef3b10
feat: Provides AbstractListenerSubscriber and ListenerSubscriberTrait
weierophinney Apr 4, 2019
b52c547
feat: Implement lazy listeners and lazy listener subscriber
weierophinney Apr 4, 2019
efec2c0
feat: update EventManager to implement ListenerProviderInterface and …
weierophinney Apr 8, 2019
c1f4cd5
feat: update EventManager to use listener providers
weierophinney Apr 9, 2019
cee468a
feat: Adds EventDispatchingInterface
weierophinney Apr 9, 2019
442d09b
feat: deprecate features that will be removed in version 4
weierophinney Apr 9, 2019
7dc24c8
feat: require container-interop ^1.2
weierophinney Apr 10, 2019
441e8dd
fix: Adapt assertion to work with PHP versions < 7.1
weierophinney Apr 10, 2019
df2b093
refactor: Make anonymous classes into actual test asset classes
weierophinney Apr 10, 2019
c367037
qa: CS fixes per phpcs
weierophinney Apr 10, 2019
af36356
docs: Updated CHANGELOG for #73
weierophinney Apr 10, 2019
357d508
fix: Adapt `yield from` statements to work in PHP 5.6
weierophinney Apr 10, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 112 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,123 @@ All notable changes to this project will be documented in this file, in reverse

- [#72](https://github.com/zendframework/zend-eventmanager/pull/72) adds support for PHP 7.3.

- [#73](https://github.com/zendframework/zend-eventmanager/pull/73) adds interfaces to allow duck-typing the EventManager as a [PSR-14](https://www.php-fig.org/psr/psr-14/) event
dispatcher. Full support is not provided yet as this version still supports
PHP 5.6. The new interfaces include:

- `Zend\EventManager\EventDispatcherInterface`
- `Zend\EventManager\ListenerProvider\ListenerProviderInterface`
- `Zend\EventManager\StoppableEventInterface`

These interfaces will be removed in version 4.0, in favor of the official
PSR-14 interfaces.

- [#73](https://github.com/zendframework/zend-eventmanager/pull/73) adds the following interfaces:
- `Zend\EventManager\EventDispatchingInterface`, for indicating a class
composes an `EventDispatcherInterface` instance. This interface will replace
the `Zend\EventManager\EventsCapableInterface` in version 4.0.
- `Zend\Expressive\ListenerProvider\PrioritizedListenerProviderInterface`,
which extends the `ListenerProviderInterface`, and adds the method
`getListenersForEventByPriority($event, $identifiers = [])`. This method
will return a list of integer priority keys mapping to lists of callable
listeners.
- `Zend\Expressive\ListenerProvider\PrioritizedListenerAttachmentInterface`,
which provides methods for attaching and detaching listeners with optional
priority values. This interface largely replaces the various related methods
in the current `EventManagerInterface`, and is for use with listener
providers.
- `Zend\Expressive\ListenerProvider\ListenerSubscriberInterface`, for
indicating that a class can attach multiple listeners to a
`PrioritizedListenerAttachmentInterface` instance. This largely replaces the
current `ListenerAggregateInterface` functionality. Users should likely use
the PSR-14 utility package's `DelegatingProvider` instead, however.

- [#73](https://github.com/zendframework/zend-eventmanager/pull/73) adds the following listener provider classes and utilities:
- `AbstractListenerSubscriber` and `ListenerSubscriberTrait` can be used to
provide a generic way to detach subscribers. In most cases,
`ListenerSubscriberInterface` implementations should define their own logic
for doing so.
- `PrioritizedListenerProvider` implements `PrioritizedListenerProviderInterface`
and `PrioritizedListenerAttachmentInterface` in order to provide the various
listener attachment and retrieval capabilities in previous versions of the
`EventManager` class.
- `PrioritizedIdentifierListenerProvider` implements `PrioritizedListenerProviderInterface`
and `SharedEventManagerInterface`, and provides all features of the
`SharedEventManager` class from previous versions of the package.
- `PrioritizedAggregateListenerProvider` implements `PrioritizedListenerProviderInterface`
and accepts a list of `PrioritizedListenerProviderInterface` instances and
optionally a generic `ListenerProviderInterface` instance to its
constructor. When retrieving listeners, it will loop through the
`PrioritizedListenerProviderInterface` instance in order, yielding from
each, and then, if present, yield from the generic
`ListenerProviderInterface` instance. This approach essentially replaces the
listener and shared listener aggregation in previous versions of the
`EventManager`.
- `LazyListener` combines the functionalities of `Zend\EventManager\LazyListener`
and `Zend\EventManager\LazyEventListener`. If no event or priority are
provided to the constructor, than the `getEvent()` and `getPriority()`
methods will each return `null`. When invoked, the listener will pull the
specified service from the provided DI container, and then invoke it.
- `LazyListenerSubscriber` implements `ListenerSubscriberInterface` and
accepts a list of `LazyListener` instances to its constructor; any
non-`LazyListener` instances or any that do not define an event will cause
th constructor to raise an exception. When its `attach()` method is called,
it attaches the lazy listeners based on the event an priority values it
pulls from them.

- [#73](https://github.com/zendframework/zend-eventmanager/pull/73) adds the static method `createUsingListenerProvider()` to the `EventManager`
class. This method takes a `ListenerProviderInterface`, and will then pull
directly from it when triggering events. If the provider also implements
`PrioritizedListenerAttachmentInterface`, the various listener attachment
methods defined in `EventManager` will proxy to it.

- [#73](https://github.com/zendframework/zend-eventmanager/pull/73) adds the static method `createUsingListenerProvider()` to the `EventManager`

### Changed

- Nothing.
- [#73](https://github.com/zendframework/zend-eventmanager/pull/73) modifies the `SharedEventManager` class to extend the new
`Zend\EventManager\ListenerProvider\PrioritizedIdentifierListenerProvider` class.

- [#73](https://github.com/zendframework/zend-eventmanager/pull/73) modifies the `EventManager` class as follows:
- It now implements each of `ListenerProviderInterface` and
`PrioritizedListenerAttachmentInterface`.
- If constructed normally, it will create a `PrioritizedListenerProvider`
instance, and use that for all listener attachment. If a
`SharedEventManagerInterface` is provided, it will create a
`PrioritizedAggregateListenerProvider` using its own
`PrioritizedListenerProvider` and the shared manager, and use that for
fetching listeners.
- Adds a `dispatch()` method as an alternative to the various `trigger*()` methods.

### Deprecated

- Nothing.
- [#73](https://github.com/zendframework/zend-eventmanager/pull/73) deprecates the following interfaces and classes:
- `Zend\EventManager\EventInterface`. Users should start using vanilla PHP
objects that encapsulate all expected behavior for setting and retrieving
values and otherwise mutating state, including how and when propagation of the
event should stop.
- `Zend\EventManager\EventManagerInterface`; start typehinting against the
PSR-14 `EventDispatcherInterface` (or, in the meantime, the package-specific
variant).
- `Zend\EventManager\EventManagerAwareInterface`
- `Zend\EventManager\EventManagerAwareTrait`
- `Zend\EventManager\EventsCapableInterface`; start using `EventDispatchingInterface` instead.
- `Zend\EventManager\SharedEventManager`; start using listener providers
instead, attaching to identifiers based on event types.
- `Zend\EventManager\SharedEventManagerInterface`
- `Zend\EventManager\SharedEventsCapableInterface`
- `Zend\EventManager\ListenerAggregateInterface`; use the new `ListenerSubscriberInterface` instead.
- `Zend\EventManager\ListenerAggregateTrait`; use the new
`ListenerSubscriberTrait`, or define your own detachment logic.
- `Zend\EventManager\AbstractListenerAggregate`; use the new
`AbstractListenerSubscriber`, or define your own detachment logic.
- `Zend\EventManager\ResponseCollection`; aggregate state in the event itself,
and have the event determine when propagation needs to stop.
- `Zend\EventManager\LazyListener`; use `Zend\EventManager\ListenerProvider\LazyListener` instead.
- `Zend\EventManager\LazyEventListener`; use `Zend\EventManager\ListenerProvider\LazyListener` instead.
- `Zend\EventManager\LazyListenerAggregate`; use `Zend\EventManager\ListenerProvider\LazyListenerSubscriber` instead.
- `Zend\EventManager\FilterChain` and the `Filter` subnamespace; these will
move to a separate package in the future.

### Removed

Expand Down
194 changes: 194 additions & 0 deletions TODO-PSR-14.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# TODO for PSR-14 implementation

## 3.3.0 forwards-compatibility release

- [x] `StoppableEventInterface` implementation
- [x] Create a `StoppableEventInterface`
- [x] Make `Event` implement it
- [x] Deprecate `propagationIsStopped()` in both `EventInterface` and `Event`
- [x] Have `Event::propagationIsStopped()` proxy to `Event::isPropagationStopped()`
- [x] Modify `EventManager` internals to use the PSR-14 method if available
- [x] Mark `StoppableEventInterface` as deprecated
- [ ] Listener provider implementation
- [x] Create a `ListenerProvider` subnamespace
- [x] Create a `ListenerProviderInterface` shim
- [x] Create a `PrioritizedListenerProvider` interface extending the
`ListenerProviderInterface` and defining a
`getListenersForEventByPriority($event, array $identifiers = []) : array<int, callable[]>` method.
- [x] Create a `PrioritizedListenerAttachmentInterface`, defining:
- [x] `attach($event, callable $listener, $priority = 1)` (where `$event`
can be an object or string name)
- [x] `detach(callable $listener, $event = null, $force = false)` (where `$event`
can be an object or string name and `$force` is boolean)
- [x] `attachWildcardListener(callable $listener, $priority = 1)`
(`attach('*', $listener, $priority)` will proxy to this method)
- [x] `detachWildcardListener(callable $listener, $force = false)`
(`detach($listener, '*', $force)` will proxy to this method)
- [x] `clearListeners($event)`
- [x] Create a `PrioritizedListenerProvider` implementation of the above based
on the internals of `EventManager`
- [x] attachment/detachment
- [x] getListenersForEvent should take into account event name if an EventInterface
- [x] getListenersForEvent should also pull wildcard listeners
- [x] getListenersForEvent should accept an optional second argument, an
array of identifiers. This method will return all listeners in prioritized
order.
- [x] implement `getListenersForEventByPriority`
- [x] Create a `PrioritizedIdentifierListenerProvider` that implements
both the `PrioritizedListenerProvider` interface and the
`SharedEventManagerInterface`
- [x] implement `getListenersForEventByPriority`
- [x] `SharedEventManager` will extend this class
- [x] mark as deprecated (will not use this in v4)
- [x] Create a `PrioritizedAggregateListenerProvider` implementation
- [x] Accepts a list of `PrioritizedListenerProvider` instances
- [x] `getListenersByEvent()` will loop through each, in order, calling the
`getListenersForEventByPriority()` method of each, returning the
aggregated listeners in priority order.
- [x] Make `SharedEventManager` an extension of `PrioritizedIdentifierListenerProvider`
- [x] Create `ListenerSubscriberInterface`
- [x] `attach(PrioritizedListenerAttachmentInterface $provider, $priority = 1)`
- [x] `detach(PrioritizedListenerAttachmentInterface $provider)`
- [x] Create `AbstractListenerSubscriber` and/or `ListenerSubscriberTrait`
- [x] define a default `detach()` implementation
- [x] Create `LazyListenerSubscriber` based on `LazyListenerAggregate`
- [x] Define an alternate LazyListener:
- [x] `__construct(ContainerInterface $container, string $event = null, int $priority = 1)`
- [x] implements functionality from both `LazyListener` and `LazyEventListener`, minus passing env to container
- [x] without an event, can be attached to any provider
- [x] with an event, can be attached to `LazyListenerSubscriber`
- [x] Constructor aggregates `LazyListener` _instances_ only
- [x] raises exception when `getEvent()` returns null
- [x] Adapter for SharedEventManagerInterface
Since we type-hint on SharedEventManagerInterface, we need to adapt generic
implementations to work as ListenerProviders.
- [x] Class that adapts SharedEventManagerInterface instances to ListenerProviders
- [x] Event Dispatcher implementation
- [x] Implement `PrioritizedListenerAttachmentInterface` (if BC)
- [x] Implement `ListenerProviderInterface` (if BC)
- [x] Create a `PrioritizedListenerProvider` instance in the `EventManger`
constructor
- [x] Decorate it in a `PrioritizedAggregateListenerProvider`
- [x] Have the various `attach()`, `detach()`, etc. methods proxy to it.
- [x] Adapt any provided `SharedEventManagerInterface` instance, and add it
to the `PrioritizedAggregateListenerProvider`
- [x] Create a named constructor that accepts a listener provider and which
then uses it internally.
- [x] If the instance is a `PrioritizedListenerAttachmentInterface`
instance, allow the attach/detach/clear methods to proxy to it.
- [x] When triggering listeners, create a `PrioritizedAggregateListenerProvider`
with the composed `PrioritizedListenerProvider` and `SharedListenerProvider` /
`PrioritizedIdentifierListenerProvider` implementations, in that order.
- [x] Replace logic of `triggerListeners()` to just call
`getListenersForEvent()` on the provider. It can continue to aggregate the
responses in a `ResponseCollection`
- [x] `triggerListeners()` no longer needs to type-hint its first argument
- [x] Create a `dispatch()` method
- [x] Method will act like `triggerEvent()`, except
- [x] it will return the event itself
- [x] it will need to validate that it received an object before calling
`triggerListeners`
- [x] Additional utilities
- [x] `EventDispatchingInterface` with a `getEventDispatcher()` method
- [x] Deprecations
- [x] `EventInterface`
- [x] `EventManagerInterface`
- [x] `EventManagerAwareInterface`
- [x] `EventManagerAwareTrait`
- [x] `EventsCapableInterface` (point people to `EventDispatchingInterface`)
- [x] `SharedEventManager`
- [x] `SharedEventManagerInterface`
- [x] `SharedEventsCapableInterface`
- [x] `ListenerAggregateInterface` (point people to the `PrioritizedListenerAttachmentInterface`)
- [x] `ListenerAggregateTrait` (point people to `ListenerSubscriberTrait`)
- [x] `AbstractListenerAggregate` (point people to `AbstractListenerSubscriber` and/or `ListenerSubscriberTrait`)
- [x] `ResponseCollection` (tell people to aggregate state/results in the event itself)
- [x] `LazyListener` (point people to `ListenerProvider\LazyListener`)
- [x] `LazyEventListener` (point people to `ListenerProvider\LazyListener`)
- [x] `LazyListenerAggregate` (point people to `ListenerProvider\LazyListenerSubscriber`)
- [x] `FilterChain` and `Filter` subnamespace (this should be done in a separate component)

## 4.0.0 full release

- [ ] Removals
- [ ] `EventInterface`
- [ ] `EventManagerInterface`
- [ ] `EventManagerAwareInterface`
- [ ] `EventManagerAwareTrait`
- [ ] `EventsCapableInterface`
- [ ] `SharedEventManager`
- [ ] `SharedEventManagerInterface`
- [ ] `SharedEventsCapableInterface`
- [ ] `ListenerAggregateInterface`
- [ ] `ListenerAggregateTrait`
- [ ] `AbstractListenerAggregate`
- [ ] `ResponseCollection`
- [ ] `LazyListener`
- [ ] `LazyEventListener`
- [ ] `LazyListenerAggregate`
- [ ] `FilterChain` and `Filter` subnamespace
- [ ] `StoppableEventInterface` (will use PSR-14 version)
- [ ] `ListenerProviderInterface` (will use PSR-14 version)
- [ ] `PrioritizedIdentifierListenerProvider`
- Changes
- [ ] `PrioritizedListenerAttachmentInterface` (and implementations)
- [ ] extend PSR-14 `ListenerProviderInterface`
- [ ] add `string` typehint to `$event` in `attach()` and `detach()`
- [ ] add `bool` typehint to `$force` argument of `detach()`
- [ ] `PrioritizedListenerProvider` interface (and implementations)
- [ ] Fulfill PSR-14 `ListenerProviderInterface`
- [ ] remove `$identifiers` argument to getListenersForEventByPriority and getListenersForEvent
- [ ] add `object` typehint to `getListenersForEventByPriority`
- [ ] `EventDispatcher`
- [ ] implement PSR-14 `EventDispatcherInterface`

## Concerns

### MVC

Currently, the MVC relies heavily on:

- event names (vs types)
- event targets
- event params
- `stopPropagation($flag)` (vs custom stop conditions in events)
- `triggerEventUntil()` (vs custom stop conditions in events)

We would need to draw attention to usage of methods that are not specific to an
event implementation, and recommend usage of other methods where available.
(We would likely keep the params implementation, however, to allow passing
messages via the event instance(s).)

Additionally, we will need to have some sort of event hierarchy:

- a base MVC event from which all others derive. This will be necessary to
ensure that existing code continues to work.
- a BootstrapEvent
- a RouteEvent
- a DispatchEvent
- a DispatchControllerEvent
- a DispatchErrorEvent
- Potentially broken into a RouteUnmatchedEvent, DispatchExceptionEvent,
MiddlewareExceptionEvent, ControllerNotFoundEvent, InvalidControllerEvent,
and InvalidMiddlewareEvent
- a RenderEvent
- a RenderErrorEvent
- a FinishEvent
- a SendResponseEvent (this one is not an MvcEvent, however)

The event names associated with each would be based on existing event names,
allowing the ability to attach using legacy names OR the class name.

We can allow using `stopPropagation()`, but have it trigger a deprecation
notice, asking users to use more specific methods of the event to stop
propagation, or, in the case of errors, raising exceptions.

- `setError()` would cause `isPropagationStopped()` to return true.
- A new method, `setFinalResponse()` would both set the response instance, as
well as cause `isPropagationStopped()` to return true.
- The `RouteEvent` would also halt propagation when `setRouteResult()` is
called.

Internally, we will also stop using the `*Until()` methods, and instead rely on
the events to handle this for us. If we need a return value, we will instead
pull it from the event on completion.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"phpbench/phpbench": "^0.13",
"phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2",
"zendframework/zend-stdlib": "^2.7.3 || ^3.0",
"container-interop/container-interop": "^1.1.0",
"container-interop/container-interop": "^1.2.0",
"zendframework/zend-coding-standard": "~1.0.0"
},
"suggest": {
Expand Down
2 changes: 1 addition & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/AbstractListenerAggregate.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@

/**
* Abstract aggregate listener
*
* @deprecated since 3.3.0. This class will be removed in version 4.0.0, in
* favor of the ListenerProvider\AbstractListenerSubscriber. In most cases,
* subscribers should fully implement ListenerSubscriberInterface on their
* own, however.
*/
abstract class AbstractListenerAggregate implements ListenerAggregateInterface
{
Expand Down
Loading