Examples
Modifying Arguments
Occasionally it can be useful to allow listeners to modify the arguments they receive so that later listeners or the calling method will receive those changed values.
As an example, you might want to pre-filter a date that you know will arrive as
a string and convert it to a DateTime
argument.
To do this, you can pass your arguments to prepareArgs()
, and pass this new
object when triggering an event. You will then pull that value back into your
method.
use DateTime;
class ValueObject
{
// assume a composed event manager
function inject(array $values)
{
$argv = compact('values');
$argv = $this->getEventManager()->prepareArgs($argv);
$this->getEventManager()->trigger(__FUNCTION__, $this, $argv);
$date = isset($argv['values']['date'])
? $argv['values']['date']
: new DateTime('now');
// ...
}
}
$v = new ValueObject();
$v->getEventManager()->attach('inject', function($e) {
$values = $e->getParam('values');
if (! $values) {
return;
}
$values['date'] = isset($values['date'])
? new DateTime($values['date'])
: new DateTime('now');
$e->setParam('values', $values);
});
$v->inject([
'date' => '2011-08-10 15:30:29',
]);
Short Circuiting
One common use case for events is to trigger listeners until either one indicates no further processing should be done, or until a return value meets specific criteria.
As an example, a request listener might be able to return a response object, and would signal to the target to stop event propagation.
$listener = function($e) {
// do some work
// Stop propagation and return a response
$e->stopPropagation(true);
return $response;
};
Alternately, the request handler could halt execution at the first listener that returns a response.
class Foo implements DispatchableInterface
{
// assume composed event manager
public function dispatch(Request $request, Response $response = null)
{
$argv = compact('request', 'response');
$results = $this->getEventManager()->triggerUntil(function($v) {
return ($v instanceof Response);
}, __FUNCTION__, $this, $argv);
}
}
Typically, you may want to return the value that stopped execution, or use it
some way. All trigger*()
methods return a ResponseCollection
instance; call
its stopped()
method to test if execution was stopped, and the last()
method
to retrieve the return value from the last executed listener:
class Foo implements DispatchableInterface
{
// assume composed event manager
public function dispatch(Request $request, Response $response = null)
{
$argv = compact('request', 'response');
$results = $this->getEventManager()->triggerUntil(function($v) {
return ($v instanceof Response);
}, __FUNCTION__, $this, $argv);
// Test if execution was halted, and return last result:
if ($results->stopped()) {
return $results->last();
}
// continue...
}
}
Assigning Priority to Listeners
One use case for the EventManager
is for implementing caching systems. As
such, you often want to check the cache early, and save to it late.
The third argument to attach()
is a priority value. The higher this number,
the earlier that listener will execute; the lower it is, the later it executes.
The value defaults to 1, and values will trigger in the order registered within
a given priority.
To implement a caching system, our method will need to trigger an event at method start as well as at method end. At method start, we want an event that will trigger early; at method end, an event should trigger late.
Here is the class in which we want caching:
class SomeValueObject
{
// assume it composes an event manager
public function get($id)
{
$params = compact('id');
$results = $this->getEventManager()->trigger('get.pre', $this, $params);
// If an event stopped propagation, return the value
if ($results->stopped()) {
return $results->last();
}
// do some work...
$params['__RESULT__'] = $someComputedContent;
$this->getEventManager()->trigger('get.post', $this, $params);
}
}
Now, let's create a ListenerAggregateInterface
implementation that can handle
caching for us:
use Laminas\Cache\Cache;
use Laminas\EventManager\EventManagerInterface;
use Laminas\EventManager\ListenerAggregateInterface;
use Laminas\EventManager\ListenerAggregateTrait;
use Laminas\EventManager\EventInterface;
class CacheListener implements ListenerAggregateInterface
{
use ListenerAggregateTrait;
private $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
public function attach(EventManagerInterface $events)
{
$this->listeners[] = $events->attach('get.pre', [$this, 'load'], 100);
$this->listeners[] = $events->attach('get.post', [$this, 'save'], -100);
}
public function load(EventInterface $e)
{
$id = get_class($e->getTarget()) . '-' . json_encode($e->getParams());
if (false !== ($content = $this->cache->load($id))) {
$e->stopPropagation(true);
return $content;
}
}
public function save(EventInterface $e)
{
$params = $e->getParams();
$content = $params['__RESULT__'];
unset($params['__RESULT__']);
$id = get_class($e->getTarget()) . '-' . json_encode($params);
$this->cache->save($content, $id);
}
}
We can then attach the aggregate to an event manager instance.
$value = new SomeValueObject();
$cacheListener = new CacheListener($cache);
$cacheListener->attach($value->getEventManager());
Now, as we call get()
, if we have a cached entry, it will be returned
immediately; if not, a computed entry will be cached when we complete the
method.