Hi everyone,
PS: Everyone is free to fork my branch and submit PR to it!
Motivation
Event manager has always been, with service manager, the slowest parts of ZF2, but the most widely used one internally. It was therefore highly important to make them faster.
Also, the event manager was a complex piece of software with a lot of obscure features, a strange shared manager concept and complex signatures.
Breaking changes
Massive performance improvements
I copy-paste some of the benchmarks I've made (the PHP7 build was an old one, so expect at least 10-15% improvments on PHP7):
ZF2 EVM
PHP 5.6
Bench\EventManagerBenchmark
Method Name Iterations Average Time Ops/second
------------------ ------------ -------------- -------------
attach20Listeners : [1,000 ] [0.0005693485737] [1,756.39326]
trigger20Listeners: [1,000 ] [0.0010452888012] [956.67341]
PHP 7
Bench\EventManagerBenchmark
Method Name Iterations Average Time Ops/second
------------------ ------------ -------------- -------------
attach20Listeners : [1,000 ] [0.0000875046253] [11,427.96734]
trigger20Listeners: [1,000 ] [0.0001518340111] [6,586.13965]
ZF3 EVM
PHP 5.6
Bench\EventManagerBenchmark
Method Name Iterations Average Time Ops/second
------------------ ------------ -------------- -------------
attach20Listeners : [1,000 ] [0.0001252155304] [7,986.22980]
trigger20Listeners: [1,000 ] [0.0002773256302] [3,605.86939]
PHP 7
Bench\EventManagerBenchmark
Method Name Iterations Average Time Ops/second
------------------ ------------ -------------- -------------
attach20Listeners : [1,000 ] [0.0000128707886] [77,695.31713]
trigger20Listeners: [1,000 ] [0.0000429670811] [23,273.63123]
No more shared event manager
I've explain myself why I think the shared event manager was a bad idea (although @ocramius tried to show me the contrary, I think the event manager should stay simple as introduced in this PR).
As I used EVM in my app, I realized I ONLY used the Shared event manager. But the SEM is SLOW AS HELL and complex (because of identifiers, expensive merging...). I think that in most use cases, people should have a global EVM used throughout their applications, and have more precise event name.
Simplifications
Previously, trigger and triggerUntil were very complex. Depending on the order and types of parameters, you could use it in a LOT of different ways. Now everything has been enforced in interfaces, so there is one possible use case, which make things clearer and more efficient (less checks).
Event no longer has name
I've simplified the EventInterface
so that it no longer has a getName
method. I'm not against reverting it, but to me, we should trigger event like this:
$evm->trigger('event.name', $eventObject);
As an event object could be used for various event names, I think it makes more sense to be used like this (and avoid to re-add logic to check if the first parameter is a string or object... that kind of things).
propagationIsStopped
has been renamed to isPropagationStopped
.
Lazy listeners
One main issue with ZF2 was the inability to have lazy listeners. This means taht when you attach listeners, they are created, with all their dependencies, on every requests, even if you don't need them.
From my app, this can add a non-negligible overhead, and solutions like proxies are annoying to setup.
Some example usage:
<?php
// The idea is that in ZF3, there will be one "application-wide" event manager registered. Nothing prevents you to
// create your own local event manager, but to replicate the shared manager behaviour, you should retrieve the
// application-wide evm
// Attaching a simple listener:
$eventManager = new EventManager();
$eventManager->attach('foo', function() {}, $priority);
// Triggering a listener
$eventManager->trigger('foo', new Event());
// Triggering a listener with a callable that will stop propagation if return to true
$eventManager->triggerUntil('foo', new Event(), function() {});
// Attaching to wildcard
$eventManager->attach('*', function() {});
// Attaching a listener and detaching it
$listener = $eventManager->attach('foo', function() {});
$eventManager->detach('foo', $listener);
// Detaching all listeners from a given event
$eventManager->detach('foo');
// Using listener aggregate to aggregate multiple events, lazily
class MyListenerAggregate implements ListenerAggregateInterface
{
public static function attachAggregate(EventManager $evm)
{
// No more confusion of getting the shared manager. Note the method is static, and
// that we pass a "spec" without instantiating the listener, as it will be created only
// on demand
$evm->attach('foo', [static::class, 'myMethod']);
}
public function myMethod(Event $event)
{
}
}
// In the module class
$evm = $serviceLocator->get(EventManager::class);
MyListenerAggregate::attachAggregate($evm); // Maybe should be called "bind" instad of attachAggregate
// No more identifiers, no more SEM... Of course, events must now be scoped, so no "created" event anymore,
// but "user.created", "application.dispatch", "controller.dispatch"...
?>