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

Conversation

weierophinney
Copy link
Member

@weierophinney weierophinney commented Apr 9, 2019

This patch provides a forwards-compatibility release that adapts zend-eventmanager to work as a PSR-14 EventDispatcher. It does so by doing the following:

  • Creating package-specific ListenerProviderInterface and EventDispatcherInterface versions that work with PHP 5.6.
  • Moving all listener aggregation into classes within a ListenerProvider subnamespace; this includes both the listener attachment previously in the EventManager instance as well as the SharedEventManager instance.
  • Renaming "listener aggregates" to "listener subscribers" in the new ListenerProvider subnamespace.
  • Adapting EventManager to consume listener providers. By default, it will create a default priority-based listener provider, and have its various listener attachment methods proxy to that provider; it then aggregates that provider with the SharedEventManager (which is itself a provider now) in order to retrieve listeners. This is done in such a way that behavior is completely backwards compatible with current usage.
    • A new named constructor, createUsingListenerProvider(), allows providing a specific listener provider for use with the EventManager. If the provider is attachment capable, the various listener attachment methods will proxy to it; otherwise, they will raise an exception.

The patch also deprecates a number of features, including:

  • Most interfaces, including the EventInterface.
  • The entire "shared event manager" concept (this can be accomplished via event object hierarchies instead)

TODO

  • Write changelog entries
  • Create documentation of new features
  • Document how to start migrating to the new features in order to prepare for version 4

Since v3 still targets PHP versions prior to PHP 7.2, this patch
provides a forwards-compatibility shim for the PSR-14
`StoppableEventInterface` via an additional, package-specific version
that is now also implemented by default in the `Event` class.
If the method `isPropagationStopped()` is defined, use it over the
`propagationIsStopped()` method.
Modifies Event::propagationIsStopped such that it now proxies to the
isPropagationStopped method, and documents in the deprecation notice
that this happens.
Creates a forwards-compatibility shim for the
`ListenerProviderInterface`, in a new subnamespace,
`Zend\EventManager\ListenerProviderInterface`.
For use in getting a lookup table of priorities and associated
listeners, optionally using identifiers for lookup.
This provides the methods necessary for attaching listeners. It does not
extend PrioritizedListenerProviderInterface, as we want to be able to
re-use that particular interface with shared providers, which will have
a different attachment mechanism in version 3 releases.
New provider implements `PrioritizedListenerAttachmentInterface` and
`PrioritizedListenerProviderInterface`, and will iterate attached
listeners in priority order.

Each iteration will take into account both the event name, if a
`getName()` method is available, the event class, and any wildcard
listeners, and listeners of the same priority will be returned in the
order they are attached, based on those criteria.
The PrioritizedIdentifierListenerProvider mimics functionality present
in the SharedEventManager (and implements the
SharedEventManagerInterface). Its purpose is to be a drop-in replacement
for the `SharedEventManager` to allow users to start migrating to PSR-14
functionality.

In the process of working on this implementation, I discovered some
complexity in the data structure returned from
`getListenersForEventByPriority` implementation of
`PrioritizedListenerProvider` that, when mimiced in
`PrioritizedIdentifierListenerProvider`, made verifying behavior
difficult. In particular, it was this line:

```php
$prioritizedListeners[$priority][] = $listOfListeners[0];
```

The problem that arose is that the `$prioritizedListeners` returned were
now two levels deep, which made comparisons far harder. I changed this
to read:

```
$prioritizedListeners[$priority] = isset($prioritizedListeners[$priority])
    ? array_merge($prioritizedListeners[$priority], $listOfListeners[0])
    : $listOfListeners[0];
```

This makes the return value far simpler, and _should_ keep speed
reasonable, though I have yet to benchmark it.
This version acts like the combination of
EventManager+SharedEventManager in terms of how it aggregates and
resolves priority for listeners.

The class aggregates a list of `PrioritizedListenerAttachmentInterface`
instances (and implements the interface itself), looping over each in
ordert to build up a prioritized list of all listeners from all
providers. Since they are done in order, the order in which they should
be attached generally is:

- PrioritizedListenerProvider
- PrioritizedIdentifierListenerProvider
…rProvider

Doing so will allow us to use it in a PrioritizedAggregateListenerProvider
within the EventManager later.

Required a couple changes to tests, as PrioritizedIdentifierListenerProvider
widens what are allowed as events and identifiers when retrieving
listeners.
This is necessary to keep feature parity with current versions, but can
be removed in version 4.
Added to the ListenerProvider namespace. Accepts a
PrioritizedListenerAttachmentInterface argument, to which it will
subscribe listeners.
Each implements ListenerSubscriberInterface::detach
Combines the features of LazyListener and LazyEventListener into
`Zend\EventManager\ListenerProvider\LazyListener`.
`LazyListenerSubscriber` is based on `LazyListenerAggregate`, but
simplifies it by having it compose `LazyListener` instances only (no
creation within it).
…PrioritizedListenerAttachmentInterface

Allows the EventManager to act as its own provider.
- Adds `Zend\EventManager\EventDispatcherInterface` as a forwards
  compatibility shim for PSR-14.
- Adds `Zend\EventManager\SharedEventManager\SharedEventManagerDecorator`,
  which decorates generic `SharedEventManagerInterface` instances as
  listener providers.
- Modifies `PrioritizedAggregateListenerProvider` to accept an optional
  `ListenerProviderInterface $default` argument. This allows
  non-prioritized `SharedEventManagerInterface` instances (such as the
  `SharedEventManagerDecorator` in the previous item) to be fallback
  providers.
- Modifies `Zend\EventManager\EventManager` as follows:
  - It now implements `EventDispatcherInterface`
  - It now composes a `$provider` property, and an optional
    `$prioritizedProvider` property. If you instantiate it per previous
    versions, it creates a `PrioritizedListenerProvider` instance and
    assigns it to the `$prioritizedProvider` property. It then checks to
    see if a shared manager was provided, and the type provided, to
    either assign the `$prioritizedProvider` as the `$provider`, or a
    `PrioritizedAggregateListenerProvider` that composes both the
    `$prioritizedProvider` and shared manager instances.
  - It adds a static named constructor, `createUsingListenerProvider()`,
    which accepts a single `ListenerProviderInterface` instance. This
    value is assigned to `$provider`, and, if it is a
    `PrioritizedListenerAttachmentInterface` instance, to the
    `$prioritizedProvider` property as well.
  - Each of the listener attachment methods (attach, detach,
    clearListeners, *WildcardListeners) now proxy to the composed
    `$prioritizedProvider`, if any. If there is none, theses methods
    now raise an exception.
  - The `getListenersForEvent()` method now proxies to the underling
    `$provider` property.
  - The `triggerListeners()` method now consumes the value of
    `getListenersForEvent()`.
  - It adds the method `dispatch($event)`, which proxies to
    `triggerListeners()`, and returns the `$event` it was passed. The
    method raises an exception of `$event` is a non-object.
  - Each of `trigger()`, `triggerUntil`, `triggerEvent`,
    `triggerEventUntil`, `getIdentifiers`, `setIdentifiers`,
    `addIdenitifers`, `getSharedManager`, `attach`, `detach`,
    `attachWildcardListener`, `detachWildcardListener`,
    `clearListeners`, and `getListenersForEvent` have been marked
    deprecated.
- Updates `EventListenerIntrospectionTrait` to work with the new
  internals of the `EventManager`.
- Updates `EventManagerTest`:
  - updates `getListenersForEvent()` to work with the new `EventManager`
    internals
  - Removes `testAttachShouldAddEventIfItDoesNotExist` as it was
    irrelevant even before the changes.
  - Removes the `testTriggeringAnEventWithAnEmptyNameRaisesAnException`
    test, as this is no longer true; you can use any object as an event
    now.
  - Modifies a few tests where they were accessing internal structures
    that have changed, while keeping the same assertions in place.
- Adds `EventManagerWithProviderTest` to demonstrate usage when creating
  an `EventManager` via its `createUsingListenerProvider()` method.
- Updates `EventListenerIntrospectionTraitTest` to work with the new
  internals of the `EventManager`.
Basically, a counterpart to the current EventManagerAwareInterface, but
for EventDispatcherInterface composition.

Also revises the Deprecations list, as we can keep EventManager as an
EventDispatcherInterface implementation for 4.0.
- `EventInterface`
- `EventManagerInterface`
- `EventManagerAwareInterface`
- `EventManagerAwareTrait`
- `EventsCapableInterface` (points people to `EventDispatchingInterface`)
- `SharedEventManager`
- `SharedEventManagerInterface`
- `SharedEventsCapableInterface`
- `ListenerAggregateInterface` (points people to the `PrioritizedListenerAttachmentInterface`)
- `ListenerAggregateTrait` (points people to `ListenerSubscriberTrait`)
- `AbstractListenerAggregate` (points people to `AbstractListenerSubscriber` and/or `ListenerSubscriberTrait`)
- `ResponseCollection` (tells people to aggregate state/results in the event itself)
- `LazyListener` (points people to `ListenerProvider\LazyListener`)
- `LazyEventListener` (points people to `ListenerProvider\LazyListener`)
- `LazyListenerAggregate` (points people to `ListenerProvider\LazyListenerSubscriber`)
- `FilterChain` and `Filter` subnamespace (this should be done in a separate component)
We now require ^1.2 to ensure that PSR-11 interfaces are also present,
allowing new classes to typehint only on the PSR-11 interfaces. This
will allow compatibility to continue as the 1.2 variants extend the
PSR-11 interfaces.
Previously, `assertInternalType('iterable')`, which only works on PHP
7.1+.
Required to allow us to test against PHP 5.6.
whitespace and long lines
@weierophinney weierophinney force-pushed the feature/psr-14-event-dispatcher-bc branch from 1e8c205 to c367037 Compare April 10, 2019 14:45
Notes all new features, major changes, and deprecations.
`yield from` was introduced in PHP 7. As such, to work in PHP 5.6, we
need to modify the statements to iterate over the inner generator and
yield results directly.
@weierophinney
Copy link
Member Author

This repository has been closed and moved to laminas/laminas-eventmanager; a new issue has been opened at laminas/laminas-eventmanager#2.

@weierophinney
Copy link
Member Author

This repository has been moved to laminas/laminas-eventmanager. If you feel that this patch is still relevant, please re-open against that repository, and reference this issue. To re-open, we suggest the following workflow:

  • Squash all commits in your branch (git rebase -i origin/{branch})
  • Make a note of all changed files (`git diff --name-only origin/{branch}...HEAD
  • Run the laminas/laminas-migration tool on the code.
  • Clone laminas/laminas-eventmanager to another directory.
  • Copy the files from the second bullet point to the clone of laminas/laminas-eventmanager.
  • In your clone of laminas/laminas-eventmanager, commit the files, push to your fork, and open the new PR.
    We will be providing tooling via laminas/laminas-migration soon to help automate the process.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant