zendframework/zf2 — pull request #5590 by devosc (2013-12-06 10:07:55+00:00)
Title: [ZF3] [WIP] Proposal - Application Events

This PR has suggestions for ZF3 with the code samples mainly isolated within the directory Zend/Framework.

*MvcEvent*
The first suggestion is breaking the MvcEvent object into separate objects where class names match the name of the event. The initial intention was to be able to clearly determine what properties are being set or should be available for a particular event, otherwise the event named classes should extend MvcEvent.

Since identifying events are more easily discerned by their event names (rather than event name + interface), I would suggest using distinct names as much as possible. E.g 'dispatch' and 'controller.dispatch' instead of using 'dispatch' for both.

Within the Application::run() method, the sequence of all of the mvc events are clearly triggered along with their dependencies. For example, I do not think the Route event should be able to, nor need to, return a response, it should only return a ResultMatch object or null.

*Events*
Another potential problem is depending on the response from the last invoked listener to provide the correct response (or object) for that event. There may still also be situations where the entire sequence of responses is needed. I think the event object should handle its own responses instead of the event manager.

Here the event's invoke method invokes the listener's invoke method and the event can add the response to a list as well as check to see whether it should set the correctly intended response.
```
public function __invoke(EventListener $listener)
{
$response = $listener($this);

$this->eventResponses[] = $response;

if ($response instanceof ViewModel) {
$this->setViewModel($response);
}

if ($this->callback) {
call_user_func($this->callback, $this, $listener, $response);
}
}
```

*Event Propagation*
The methods to check and stop an the propagation should not be used for anything other than indicating to the EM that the EM should stop executing any remaining listeners, i.e those methods should not be used as part of any application or business logic.

```
public function trigger($event)
{
$event->setTarget($event->getTarget())
->stopPropagation(false);

$listeners = $event->getListeners() ?: $this->getEventListeners($event);

foreach($listeners as $listener) {
$event($listener);

if ($event->propagationIsStopped()) {
break;
}
}
}
```

*Event Manager*
One of the slowest parts of the event manager is merging the prioritized list of events from the Shared EM. For this reason alone I would suggest removing the Shared EM completely and having a single EM to be injected into any component that requires one.

The EM will group its listeners by event name, and thus could be more efficient when the event names are even more distinct.

An EM can be attached to another EM, in which case will it merge the prioritized listeners upon triggering an event.

The attach and trigger methods accept a single argument for either an event or listener, respectively. @weierophinney there can still be convenience methods to assist in creating generic event and listener objects.

*Event Targets*
The default target for all EMs is the wildcard event name. Events will specify their target(s) and event listeners will specify the targets that they are listening to. This allows $this to be specified as the target for an event, and listeners can specify any sub class string name for the target it is listening to.

```
$listener = new CallbackListener(array($this, 'onBootstrap'), MvcEvent::EVENT_BOOTSTRAP, null, 10000);

$em->attach($listener);
```

```
$event = new BootstrapEvent();

$event->setTarget($this)
->setApplication($this)
->setRequest($this->request)
->setResponse($this->response)
->setRouter($this->router)
->setControllerLoader($this->controllerLoader)
->setViewModel(new ViewModel);

$em->trigger($event);
```

*Events and Listeners*
Each event and listener is an object which makes it no different than before, however the difference here is that these objects are being instantiated before they are passed to the EM.

By allowing an event to manage the responses from it listeners, they can also manage whether or not it should continue propagating. Each event object has a default callback method which can also stop the propagation of the event.

*Service Manager*
An SM should only be concerned with handling the requests for services and not have any knowledge of how those services are actually created. The SM will manage its configuration and any shared services or listeners. It will be up to the request handler to determine how to locate and instantiate the service. @Ocramius, although I previously suggested having a service request object I'm now thinking (from a dependency and testing point of view) that the variations of these service requests can be plugin managers which uses the SM via composition in order to retrieve its configuration or any other services.

Currently, the Zend\Framework\ServiceManager SM invokes its service requests similar to EMs via the invoke method, however this can cause a conflict when a listener is also an invokable service request so the createService method should probably be used for its interface (or maybe just name the method factory instead).

*Configuration*
I also agree with @bakura10 that a components configuration should be made available similar to a modules configuration. The configuration compilation process should be intelligent enough to transform a human friendly configuration file into a slightly more consumable (optimized) format, e.g the compilation process would perform the actions that a components Config object would (during the compilation process itself).

*Action Controller Listeners*
The main dispatch listener now invokes controllers as if they are listeners. This transforms each previous controller action method into an independent action controller (or listener), although this creates more classes it makes dependency injection a more efficient and easier to test. However it should not be too difficult to implement multiple actions per controller as previously done.

*Notes*
Most of the new code is in the Zend/Framework directory, however Http/ViewManager also got some revisions (to make things a little clearer). Hopefully there might be some ideas here that are of interest, or even better, something to build on.