Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fc5b5dc
feat: StoppableEventInterface implementation
weierophinney Apr 2, 2019
9d066f1
feat: prefer `isPropagationStopped` over `propagationIsStopped`
weierophinney Apr 2, 2019
719b365
feat: proxy to isPropagationStopped
weierophinney Apr 2, 2019
17893fc
docs: provide comprehensive checklist for PSR-14 adoption
weierophinney Apr 2, 2019
10ec82b
feat: Creates PrioritizedListenerProviderInterface
weierophinney Apr 3, 2019
368fe20
feat: Creates PrioritizedListenerAttachmentInterface
weierophinney Apr 3, 2019
65a410d
feat: Creates PrioritizedListenerProvider
weierophinney Apr 3, 2019
48c5ab5
feat: Creates PrioritizedIdentifierListenerProvider
weierophinney Apr 3, 2019
23a7bd9
feat: Creates a PrioritizedAggregateListenerProvider
weierophinney Apr 4, 2019
625138d
refactor: Have SharedEventManager extend PrioritizedIdentifierListene…
weierophinney Apr 4, 2019
6051761
feat: return listener from attach, attachWildcardListener methods
weierophinney Apr 4, 2019
84739b0
feat: adds a ListenerSubscriberInterface
weierophinney Apr 4, 2019
109c2bc
feat: Provides AbstractListenerSubscriber and ListenerSubscriberTrait
weierophinney Apr 4, 2019
00501ad
feat: Implement lazy listeners and lazy listener subscriber
weierophinney Apr 4, 2019
046ddd2
feat: update EventManager to implement ListenerProviderInterface and …
weierophinney Apr 8, 2019
736eb02
feat: update EventManager to use listener providers
weierophinney Apr 9, 2019
79b7a5c
feat: Adds EventDispatchingInterface
weierophinney Apr 9, 2019
f926f4e
feat: deprecate features that will be removed in version 4
weierophinney Apr 9, 2019
5567293
feat: require container-interop ^1.2
weierophinney Apr 10, 2019
ce9c77a
refactor: Make anonymous classes into actual test asset classes
weierophinney Apr 10, 2019
861f30a
qa: CS fixes per phpcs
weierophinney Apr 10, 2019
4994c23
docs: Updated CHANGELOG for 3.4.0 release
weierophinney Apr 10, 2019
48649a9
qa: ensure tests pass, and BC breaks identified and removed
weierophinney Jan 8, 2021
b34b921
qa: apply automated CS fixes
weierophinney Jan 8, 2021
1408376
feat: mark package as a PSR-14 implementation
weierophinney Jan 8, 2021
3682715
qa: bump PHPUnit version
weierophinney Jan 13, 2021
373452e
qa: prophecy 1.12.0 is required to work with PHP 8
weierophinney Jan 13, 2021
060fcaf
qa: resolve merge conflict in EventManagerTest
weierophinney Jan 21, 2021
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
72 changes: 69 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,79 @@ All notable changes to this project will be documented in this file, in reverse

- Nothing.

- Adds the ability to use the EventManager as a [PSR-14](https://www.php-fig.org/psr/psr-14/) event dispatcher.

- Adds the following interfaces:
- `Laminas\EventManager\EventDispatchingInterface`, for indicating a class composes an `EventDispatcherInterface` instance.
This interface will replace the `Laminas\EventManager\EventsCapableInterface` in version 4.0.

- `Laminas\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.

- `Laminas\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.

- `Laminas\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.

- 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
the 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.

- 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.

- Adds the static method `createUsingListenerProvider()` to the `EventManager`.

### Changed

- Nothing.
- Modifies the `SharedEventManager` class to extend the new `Laminas\EventManager\ListenerProvider\PrioritizedIdentifierListenerProvider` class.

- Modifies the `EventManager` class as follows:
- It now implements each of the PSR-14 `ListenerProviderInterface` and the new `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.
- Deprecates the following interfaces and classes:
- `Laminas\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.

- `Laminas\EventManager\EventManagerInterface`; start typehinting against the PSR-14 `EventDispatcherInterface`.
- `Laminas\EventManager\EventManagerAwareInterface`
- `Laminas\EventManager\EventManagerAwareTrait`
- `Laminas\EventManager\EventsCapableInterface`; start using `EventDispatchingInterface` instead.
- `Laminas\EventManager\SharedEventManager`; start using listener providers instead, attaching to identifiers based on event types.
- `Laminas\EventManager\SharedEventManagerInterface`
- `Laminas\EventManager\SharedEventsCapableInterface`
- `Laminas\EventManager\ListenerAggregateInterface`; use the new `ListenerSubscriberInterface` instead.
- `Laminas\EventManager\ListenerAggregateTrait`; use the new `ListenerSubscriberTrait`, or define your own detachment logic.
- `Laminas\EventManager\AbstractListenerAggregate`; use the new `AbstractListenerSubscriber`, or define your own detachment logic.
- `Laminas\EventManager\ResponseCollection`; aggregate state in the event itself, and have the event determine when propagation needs to stop.
- `Laminas\EventManager\LazyListener`; use `Laminas\EventManager\ListenerProvider\LazyListener` instead.
- `Laminas\EventManager\LazyEventListener`; use `Laminas\EventManager\ListenerProvider\LazyListener` instead.
- `Laminas\EventManager\LazyListenerAggregate`; use `Laminas\EventManager\ListenerProvider\LazyListenerSubscriber` instead.
- `Laminas\EventManager\FilterChain` and the `Filter` subnamespace; these will move to a separate package in the future.

### Removed

Expand Down Expand Up @@ -101,7 +167,7 @@ All notable changes to this project will be documented in this file, in reverse
- [zendframework/zend-eventmanager#17](https://github.com/zendframework/zend-eventmanager/pull/17) makes a
number of internal changes to how listeners are stored in order to improve
performance, by as much as 10% in the scenario used in the MVC layer.

Additionally, it optimizes when the target and event arguments are injected
into an event, eliminating that step entirely when either is unavailable.

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

## 3.4.0 forwards-compatibility release

- [x] `StoppableEventInterface` implementation
- [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] Listener provider implementation
- [x] Create a `ListenerProvider` subnamespace
- [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.
11 changes: 8 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,23 @@
},
"require": {
"php": "^7.3 || ^8.0",
"laminas/laminas-zendframework-bridge": "^1.0"
"laminas/laminas-zendframework-bridge": "^1.0",
"psr/event-dispatcher": "^1.0"
},
"require-dev": {
"container-interop/container-interop": "^1.1",
"container-interop/container-interop": "^1.2",
"laminas/laminas-coding-standard": "~1.0.0",
"laminas/laminas-stdlib": "^2.7.3 || ^3.0",
"phpbench/phpbench": "^0.17.1",
"phpspec/prophecy": "^1.12",
"phpspec/prophecy-phpunit": "^2.0",
"phpunit/phpunit": "^9.4.1"
},
"provide": {
"psr/event-dispatcher-implementation": "^1.0"
},
"suggest": {
"container-interop/container-interop": "^1.1, to use the lazy listeners feature",
"container-interop/container-interop": "^1.2, to use the lazy listeners feature",
"laminas/laminas-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature"
},
"autoload": {
Expand Down
5 changes: 5 additions & 0 deletions src/AbstractListenerAggregate.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@

/**
* Abstract aggregate listener
*
* @deprecated since 3.4.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
15 changes: 14 additions & 1 deletion src/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
namespace Laminas\EventManager;

use ArrayAccess;
use Psr\EventDispatcher\StoppableEventInterface;

use function gettype;
use function is_array;
Expand All @@ -21,7 +22,7 @@
* Encapsulates the target context and parameters passed, and provides some
* behavior for interacting with the event manager.
*/
class Event implements EventInterface
class Event implements EventInterface, StoppableEventInterface
{
/**
* @var string Event name
Expand Down Expand Up @@ -196,9 +197,21 @@ public function stopPropagation($flag = true)
/**
* Is propagation stopped?
*
* @deprecated Use isPropagationStopped instead, to make your application
* forwards-compatible with PSR-14 and zend-eventmanager v4. If you
* plan to override this method, please do so via the `isPropagationStopped`
* method, as this method proxies to that one starting in version 3.4.0.
* @return bool
*/
public function propagationIsStopped()
{
return $this->isPropagationStopped();
}

/**
* {@inheritDoc}
*/
public function isPropagationStopped(): bool
{
return $this->stopPropagation;
}
Expand Down
Loading