ZF2 EventManager

Slightly free translation of article about the EventManager in the Zend Framework 2 blog Matthew Weier O Phinney.
The article in the examples, says that this Zend\EventManager, how to use it, what are the benefits of event-driven way of solving programming tasks in PHP. About what new things we can expect in ZF2.
The original and the translation was written with the release of zf2.dev4 before .beta1, significant changes have occurred. But still the article should be used for reference, nothing more.

Terminology


the

    Event Manager (event Manager)
    an object that aggregates event handlers (Listener) for one or more named events (Event) and also initiates the processing of these events.

    Listener (event Handler)
    a function/method callback.

    Event (Event)
    the action of initiation which starts the execution of certain event handlers


The event is an object that contains the data, when and how it was initiated: which object it was called, passed parameters, etc. the Event also has a name that allows you to bind handlers to a specific event referring to the event name.

Start


The minimum things needed to run all this:
the
    the
  • an instance of the class EventManager.
  • the
  • One or more event handler bound to one or more events.
  • the
  • Call EventManager::trigger() to trigger the event.

the
use Zend\EventManager\EventManager;

$events = new EventManager();

$events- > attach('do', function($e) {
$event = $e->getName();
$params = $e->getParams();
printf(
'Handled event "%s", with parameters %s',
$event,
json_encode($params)
);
});

$params = array('foo' => 'bar', 'baz' => 'bat');
$events- > trigger('do', null, $params);


The output will get:
the
Handled event "do", with parameters {"foo":"bar","baz":"bat"}

Nothing complicated!
note: the examples use an anonymous function, but you can use the name of a function, static class method or object method.

But what is the second argument of 'null' in method $events- > trigger()?

As a rule, the object EventManager is used within a class, and the event is triggered within any method of this class. This second argument is the "context" or "purpose", and in the case described, would be an instance of that class. This gives access to event handlers for request object that sometimes can be useful/necessary.

the
use Zend\EventManager\EventCollection,
Zend\EventManager\EventManager;

class Example
{
protected $events;

public function setEventManager(EventCollection $events)
{
$this- > events = $events;
}

public function events()
{
if (!$this->events) {
$this- > setEventManager(new EventManager(
array(__CLASS__, get_called_class())
);
}
return $this->events;
}

public function do($foo, $baz)
{
$params = compact('foo', 'baz');
$this->events ()- > trigger(__FUNCTION__, $this, $params);
}

}

$example = new Example();

$example->events()->attach('do', function($e) {
$event = $e->getName();
$target = get_class($e- > getTarget()); // "Example"
$params = $e->getParams();
printf(
'Handled event "%s" on target "%s", with parameters %s',
$event,
$target
json_encode($params)
);
});

$example- > do('bar', 'bat');


This example essentially does the same thing as the first. The main difference is that the second argument of the method trigger(), we pass to the handler a context (the object that triggered the processing of this event) and the handler gets it via the method $e- > getTarget() and can do with them anything (within reason :) ).

You may have 2 questions:
the
    the
  • What is EventCollection?
  • the
  • And what kind of arguments we pass to the constructor EventManager?

The response further.

EventCollection vs EventManager


One of the principles that I try to follow ZF2's the Liskov substitution Principle. The interpretation of this principle can be as follows: For any class, which in the future may need to override another class must be defined “basic” interface. And it allows developers to use a different implementation of a class by defining the methods of this interface.
Therefore developed interface EventCollection that describes an object capable of aggregating listeners for events, and initiation of these events. the EventManager — the standard implementation, which will be included in ZF2.

StaticEventManager


One aspect that the EventManager implementation provides is the ability to interact with the StaticEventCollection. This class allows you to attach handlers not just to named events, but to events initiated by a specific context or purpose. the EventManager, if processing of events takes event handlers (signed on the current context) from the object StaticEventCollection and executes them.

How does it work?

the
use Zend\EventManager\StaticEventManager;

$events = StaticEventManager::getInstance();
$events- > attach('Example', 'do', function($e) {
$event = $e->getName();
$target = get_class($e- > getTarget()); // "Example"
$params = $e->getParams();
printf(
'Handled event "%s" on target "%s", with parameters %s',
$event,
$target
json_encode($params)
);
});


This example is almost identical to the previous one. The only difference is that the first argument to the method attach(), we pass a context 'Example' to which I would like to add our handler. In other words when processing an event 'do' if this event is triggered by the context 'Example', then call our handler.

This is the place where the constructor parameters EventManager play the role. The constructor allows you to pass a string, or an array of strings defining the name/names of contexts that need to take event handlers from the StaticEventManager. If you pass an array of contexts, then all event handlers of these contexts will be performed. Event handlers attached directly to EventManager will be executed before the handlers defined in StaticEventManager.

Combine the definition of the Example class and a static event handler of 2 recent examples, and complete the following:

the
$example = new Example();
$example- > do('bar', 'bat');


The output will get:
the
Handled event "do" on target "Example", with parameters {"foo":"bar","baz":"bat"}

Now extend the Example class:

the
class SubExample extends Example
{
}


Note the parameters that we pass to the constructor EventManager is an array of __CLASS__ and get_called_class(). This means that when calling do() of class SubExample, our event handler is also executed. If we in the designer only specified 'SubExample’, our handler will be called only when SubExample::do() but not in Example::do().

The names used as contexts or targets need not be class names; you can use arbitrary names. For example, if you have a set of classes that are responsible for Caching or logging, you can name contexts as "log" and "cache" and use these names rather than class names.

If you do not want the event Manager processed the static events, you can pass the parameter null method of setStaticConnections():

the
$events->setStaticConnections(null);


To connect back the processing of static events:

the
$events->setStaticConnections(StaticEventManager::getInstance());


Listener Aggregates


You may need to sign a class on handling multiple events, and in this “class handler” to define methods to handle some events. To do this, you can implement in your “class handler” interface HandlerAggregate. This interface defines 2 methods attach(EventCollection $events) and detach(EventCollection $events).

(I do not understand what is translated, the example below is more intuitive).

the
use Zend\EventManager\Event
Zend\EventManager\EventCollection,
Zend\EventManager\HandlerAggregate,
Zend\Log\Logger;

class LogEvents implements HandlerAggregate
{
protected $handlers = array();
protected $log;

public function __construct(Logger $log)
{
$this->log = $log;
}

public function attach(EventCollection $events)
{
$this->handlers[] = $events- > attach('do', array($this, 'log'));
$this->handlers[] = $events- > attach('doSomethingElse', array($this, 'log'));
}

public function detach(EventCollection $events)

foreach ($this- > handlers as $key => $handler) {
$events- > detach($handler);
unset($this->handlers[$key];
}
$this->handlers = array();
}

public function log(Event $e)
{
$event = $e->getName();
$params = $e->getParams();
$log- > info(sprintf('%s: %s', $event, json_encode($params)));
}
}


To add this handler in the event Manager use:

the
$doLog = new LogEvents($logger);
$events->attachAggregate($doLog);


and any event that needs processing our handler (LogEvents) will be treated in the appropriate method of the class. This allows you to define complex event handlers in one place (stateful handlers).

Pay attention to the method detach(). In the same way as attach() as argument it takes the object EventManager, and “detaches” all handlers (from our array of handlers — $this->handlers[]) of the event Manager. This is possible because EventManager::attach() returns an object representing a handler — which we are ‘attached’ earlier in the method LogEvents::attach().

Result handlers


You may need to get the result of event handlers. You need to keep in mind that one event can be signed by multiple handlers. And the result of each processor must not conflict with the results of other handlers.

the EventManager returns the object ResponseCollection. This class inherits from the class SplStack, and gives you access to the results of all handlers (the Result of the last handler will be the beginning of the stack).

ResponseCollection, in addition to the methods SplStack has additional methods:
the
    the
  • first() — the execution result of the first handler
  • the
  • last() — the result of the last handler
  • the
  • contains($value) — check the result in the stack results, returns true/false.

Typically, when you trigger the event handling, you should not be heavily dependent on the result handlers. Moreover, when the initiation of the event, you can't always be sure which event handler will be subscribed to this event (maybe even there will be no handler, and no result, you will not get). However, you have the ability to abort handlers if the required result has already been obtained in one of the handlers.

Interrupt event handling


If one of the handlers got the result expected by the initiator of the event; or the processor decides that something goes wrong; or one of the handlers, for whatever reason, decide that you do not need to fulfill the following handlers — you have a mechanism to break the ‘stack’ event handlers.

An example where this may be necessary can serve as a caching mechanism based on the EventManager. At the beginning of your method, you initiate the event “the search of data in the cache”, and if one of the handlers will find him responsible in the cache for the desired data, then aborts execution of other handlers, and you return the data fetched from the cache. If not, then you generate the data and fire the event “write cache”

the EventManager provides two ways to do this. The first way is to use a special method triggerUntil(), which checks the result of each executed processor, and if the result meets certain requirements, then execution of subsequent handlers is aborted.

Example:

the
public function someExpensiveCall($criteria1, $criteria2)
{
$params = compact('criteria1', 'criteria2');
$results = $this->events()->triggerUntil(__FUNCTION__, $this, $params, function ($r) {
return ($r instanceof SomeResultClass);
});
if ($results- > stopped()) {
return $results- > last();
}

// ... do some work ...
}


the arguments to the method triggerUntil() are like the arguments to the method trigger(), with the exception of an additional argument at the end of the callback function, which verifies the performance of each handler and if it returns true, then execution of subsequent handlers is aborted.
Following this way, we know that the probable cause of the interrupt event processing is that the result of the last executed handler meets our criteria.

Another way to interrupt the processing of an event is the use of method the stoppropagation(true) in the body of the handler. That will cause the event Manager to stop the execution of subsequent handlers.

the
$events- > attach('do', function ($e) {
$e->the stoppropagation();
return new SomeResultClass();
});


With this approach, you can be sure that event processing is interrupted because of compliance of the work product handler our criteria.

the Order of execution of handlers


You may want to specify the order of execution of handlers. For example, you want to handler, host Log, guaranteed to be executed despite the fact that other handlers can abort processing of this event at any time. Or if you implement Caching: a handler that searches the cache was executed before the other; and the processor, the leading entry in the cache, on the contrary, was carried out later.

the EventManager::attach() and StaticEventManager::attach() have an optional argument priority (the default is 1), with which you can control the execution priority of handlers. A handler with higher priority are executed before handlers with a lower priority.

the
$priority = 100;
$events- > attach('Example', 'do', function($e) {
$event = $e->getName();
$target = get_class($e- > getTarget()); // "Example"
$params = $e->getParams();
printf(
'Handled event "%s" on target "%s", with parameters %s',
$event,
$target
json_encode($params)
);
}, $priority);


Matthew Weier O Phinney recommends use priorities only when absolutely necessary. And I agree with him.

putting it all together: a Simple caching mechanism


In the previous sections it was written that using events and interrupts the processing thereof — an interesting way to implement a caching mechanism in the application. Let's create a full example.

First define a method that could use caching.

Matthew Weier O Phinney in their examples often as the name of the event uses the __FUNCTION__, and thinks it is a good practice, because you can easily write a macro to trigger events, and allows to determine the uniqueness of these names (especially since the context usually is the class of initiating event). And to separate events generated by one method, use the Postfix style "do.pre", "do.post", "do.error", etc.

In addition, $params passed to the event — is a list of arguments passed to the method. This is because the parameters are not stored in the object, and handlers may not get the options they want out of context. But the question remains, how to call the resulting setting for an event that writes to the cache? The example uses __RESULT__, which is convenient because a double underline with two sides, usually reserved by the system.

Our method might look something like this:

the
public function someExpensiveCall($criteria1, $criteria2)
{
$params = compact('criteria1', 'criteria1');
$results = $this->events()->triggerUntil(__FUNCTION__ . '.pre', $this, $params, function ($r) {
return ($r instanceof SomeResultClass);
});
if ($results- > stopped()) {
return $results- > last();
}

// ... do some work ...

$params['__RESULT__'] = $calculatedResult;
$this->events ()- > trigger(__FUNCTION__ . '.post', $this, $params);
return $calculatedResult;
}


Now define the event handlers working with cache. We need to attach handlers to the events 'someExpensiveCall.pre' and 'someExpensiveCall.post'. In the first case, if the data is found in cache, we return them. In the past, we keep data in the cache.

We also assume that the variable $cache defined earlier, and is similar to the Zend_Cache object. Handler 'someExpensiveCall.pre' we set a priority of 100, to ensure the execution of the handler before the others, and for 'someExpensiveCall.post' the priority of -100, if other handlers want to modify data before writing to the cache.
the
$events- > attach('someExpensiveCall.pre', function($e) use ($cache) {
$params = $e->getParams();
$key = md5(json_encode($params));
$hit = $cache->load($key);
return $hit;
}, 100);

$events- > attach('someExpensiveCall.post', function($e) use ($cache) {
$params = $e->getParams();
$result = $params['__RESULT__'];
unset($params['__RESULT__']);
$key = md5(json_encode($params));
$cache->save($result, $key);
}, -100);

note: we mogliby define HandlerAggregate, and store $cache in class property and not import it into an anonymous function.


Of course, we could implement a caching mechanism on the object itself, and not to make to the event handler. But this approach gives us the ability to plug in handlers for caching to other events (to implement a caching mechanism for other classes, keeping the logic of fetching from the cache and saving it to the cache in one place), or to attach other handlers to these events (which would be done for example by logging or validation). The fact is that if you design your class using the event — you make it more flexible and extensible.

Opinion


the EventManager is a new and powerful addition to Zend Framework. Now it is used with the new MVC prototype for the empowerment of some of its aspects. After the release of ZF2 event model, I am sure, will be very popular.

Of course, there is some roughness, on removing which the people work.

I'd add — nothing really new there, it's nice that such a thing will appear in Zende — I will definitely use.
I think the text is saturated with terms and hard to read (partly because of the small experience in translation of articles).
I have nothing against criticism.

Original: http://weierophinney.net/matthew.
Article based on information from habrahabr.ru

Comments

Popular posts from this blog

Powershell and Cyrillic in the console (updated)

Active/Passive PostgreSQL Cluster, using Pacemaker, Corosync

Automatic deployment ElasticBeanstalk using Bitbucket Pipelines