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.