Содержание


EventManager


EventManager(Менеджер событий), ZF2



Обзор

EventManager (менеждер событий) это компонент, предназначенный для использования следующих случаях:

1) Реализация простой объектной/обзорной модели

2)Реализация аспектно-ориентированного дизайна.

3)Реализация управляемой событиями архитектуры.

Базовая архитектура дает возможность присоединения/отсоединения слушателей к ранее обозначенным событиям, на основе экземпляров коллекций, триггеров событий и прерываний.

Быстрый старт

Обычно EventManager создается в классе:

use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\EventManager;
use Zend\EventManager\EventManagerAwareInterface;
 
class Foo implements EventManagerAwareInterface
{
    protected $events;
 
    public function setEventManager(EventManagerInterface $events)
    {
        $events->setIdentifiers(array(
            __CLASS__,
            get_called_class(),
        ));
        $this->events = $events;
        return $this;
    }
 
    public function getEventManager()
    {
        if (null === $this->events) {
            $this->setEventManager(new EventManager());
        }
        return $this->events;
    }
}

Пример выше позволяет получить экземпляр EventManager или сбросить его и заменить новым экземпляром. Если же его еще не существует, то будет создан лениво по0требованию.

EventManager есть смысл применять только в том случае, если он действительно вызывает какие-то события. В обычном вызове события участвует три аргумента:

1) Имя события, которое обычно совпадает с текущим методом/функцией.

2) «context», который обычно представляет собой текущий экземпляр объекта

3) аргументы, которые обычно являются аргументами к текущей функции/методу.

class Foo
{
    // ... assume events definition from above
 
    public function bar($baz, $bat = null)
    {
        $params = compact('baz', 'bat');
        $this->getEventManager()->trigger(__FUNCTION__, $this, $params);
    }
}

В свою очередь запуск событий (triggering events) интересно (обосновано) использовать, когда есть слушатели возможных событий. Слушатели прикрепляются к EventManager, основываясь на имени события и функции обратного вызова (callback) для уведомления.

Функции обратного вызова (callback) получает объект события (Event), у которого есть возможность получить имя события, context и параметры. Добавим слушателя и вызовем событие:

use Zend\Log\Factory as LogFactory;
 
$log = LogFactory($someConfig);
$foo = new Foo();
$foo->getEventManager()->attach('bar', function ($e) use ($log) {
    $event  = $e->getName();
    $target = get_class($e->getTarget());
    $params = json_encode($e->getParams());
 
    $log->info(sprintf(
        '%s called on %s, using params %s',
        $event,
        $target,
        $params
    ));
});
 
// Results in log message:
$foo->bar('baz', 'bat');
// reading: bar called on Foo, using params {"baz" : "baz", "bat" : "bat"}"

Учтите, что второй аргумент attach() должен являться любой валидной (действительной) функцией обратного вызова (callback). В примере используется анонимная функция (anonymous function) для того, что б он был самодостаточный (полный). Однако Вы можете использовать любое валидное имя функции, функциональный объект (function object, functor), строка ссылающаяся на статический метод, array callback with a named static method or instance method. Подойдет любой PHP callback.

Иногда нужно создать слушателя, когда еще нет объекта экземпляра EventManager. Zend Framework позволяет это сделать используя понятие SharedEventCollection. Вы можете объединить экземпляр EventManager с известным SharedEventCollection, и  экземпляр EventManager будет запрашивать его для дополнительных слушателей. Слушатели прикрепленные к SharedEventCollection работают в общем точно так же, как и обычные менеджеры событий, но требуют еще один дополнительный параметр при вызове: именованный экземпляр (a named instance).

В примере создания EventManager мы использовали __ CLASS__. Эта значение или любая строка записанная в массиве, переданном в конструктор, может быть использована как идентификатор при использовании SharedEventCollection. Например, предполагая, что у нас есть экземпляр SharedEventManager, который был инъекцирован в экземпляр EventManager (например через Di) изменим наш пример для использования с SharedEventCollection:

use Zend\Log\Factory as LogFactory;
 
// Assume $events is a ZendEventManagerSharedEventManager instance
 
$log = LogFactory($someConfig);
$events->attach('Foo', 'bar', function ($e) use ($log) {
    $event  = $e->getName();
    $target = get_class($e->getTarget());
    $params = json_encode($e->getParams());
 
    $log->info(sprintf(
        '%s called on %s, using params %s',
        $event,
        $target,
        $params
    ));
});
 
// Later, instantiate Foo:
$foo = new Foo();
$foo->getEventManager()->setSharedEventCollection($events);
 
// And we can still trigger the above event:
$foo->bar('baz', 'bat');
// results in log message:
// bar called on Foo, using params {"baz" : "baz", "bat" : "bat"}"

Важно: StaticEventManager. Выможетеиспользовать StaticEventManager singleton как SharedEventCollection. Таким образом Вам не нужно будет беспокоиться как и где получить доступ к SharedEventCollection. Глобально можно вызвать так: StaticEventManager::getInstance().

EventManager позволяет отделять слушателей друг от друга: проверяя возвращаемое значение, перебором и сравнением возвращаемых значений нескольких слушателей, установкой приоритетов и другие. В примерах будут показаны несколько таких возможностей.

Иногда приходиться вешать на одного слушателя несколько событий или даже все события данного экземпляра:

Присоединение многих событий сразу:

$events = new EventManager();
$events->attach(array('these', 'are', 'event', 'names'), $callback);

Не забывайте, если объявите приоритет, то он будет применен ко всем объявленным событиям сразу.

Присоединение с помощью шаблона:

$events = new EventManager();
$events->attach('*', $callback);

При указании приоритета он будет использоваться для каждого сработанного события.

Тоесть любое вызванное события будет уведомлять об этом слушателя.

Присоединение ко многим событиям сразу через SharedEventManager

$events = new SharedEventManager();
// Attach to many events on the context "foo"
$events->attach('foo', array('these', 'are', 'event', 'names'), $callback);
 
// Attach to many events on the contexts "foo" and "bar"
$events->attach(array('foo', 'bar'), array('these', 'are', 'event', 'names'), $callback);
$events = new SharedEventManager();
// Attach to all events on the context "foo"
$events->attach('foo', '*', $callback);
 
// Attach to all events on the contexts "foo" and "bar"
$events->attach(array('foo', 'bar'), '*', $callback);

Настройка

Опции EventManager

1) identifier – строка или массив строк, которые будут переданы в EventManager при организации связи через SharedEventManager

2) event_class -  имя класса Event для представления события, переданных в слушатель.

3) shared_collections -  экземпляр SharedEventCollection, используемый при выборе событий.

Доступные методы

1) __construct

__construct(null|string|int $identifier)

Создает новый экземпляр EventManager, используя переданный идентификатор (identifier), если это предусмотрено в пределах коллекции (collections).


2) setEventClass

setEventClass(string $class)

Указывает имя класса Event, используемого при передаче события в слушатель (listeners).


3) setSharedCollections

setSharedCollections(SharedEventCollection $collections = null)

Экземпляр SharedEventCollection, используемый при срабатывании(запуске) события.

 

4) getSharedCollections

getSharedCollections()

Возвращает текущий прикрепленный экземпляр SharedEventCollection. Если не было присоединено ни одной коллекции то вернется  NULL.

 

5) trigger

trigger(string $event, mixed $target, mixed $argv, callback $callback)

Триггеры(переключатели) всех слушателей назначенных событий (их имен). Рекомендуется использовать имя функции/метода для $event, добавляя к нему «.pre», «.post» и другие по мере необходимости. $context должен быть экземпляром текущего объекта, или имя функции, если не вызывается внутри объекта. $params – ассоциативный массив или экземпляр ArrayAccess. Хорошим тоном является использование параметров, передаваемых в функции/метод. Для этого может быть полезен метод compact(). Он так же может принимать callback (обратного вызова) функцию и вести себя как triggerUntil().

 Этот метод возвращает экземпляр ResponseCollection, который может быть использован для анализа возвращаемых значений различных слушателей, тестирования короткого замыкания (test for short-circuiting) и другого.

 

6) triggerUntil

triggerUntil(string $event, mixed $context, mixed $argv, callback $callback)

 Триггеры всех слушателей, объявленных событий. Похож на trigger() с той лишь разницей, что возвращаемые значения от слушателей попадают в $callback, и если возвращается TRUE, то слушание прерывается. Это можно протестировать используя $result->stopped().

 

7) attach

attach(string $event, callback $callback, int $priority)

Присоединяет $callback к объекту EventManager, прослушивая событие $event. Если свойство  $priority установлено, то слушатель будет добавлен во внутренний стек в соответствии с приоритетом. Чем выше значение, тем быстрее выполнится. По умолчанию все приоритеты установлены в значение «1», отрицательные значения не допустимы.

 Этот метод возвращает экземпляр Zend\Stdlib\CallbackHandler. При необходимости это значение может быть передано в  detach().

 

8) attachAggregate

attachAggregate(string|ListenerAggregate $aggregate)

Если в $aggregate передана строка, то создается экземпляр этого класса. Далее $aggregate будеи передан в  EventManager и применен метод attach(). Тоесть так можно регистировать слушателей.

Возвращаемое значение – экземпляр ListenerAggregate.

 

9) detach

detach(CallbackHandler $listener)

Сканирует всех слушателей и отключает, тот, который передан в $listener. Возвращает булевое значение. TRUE в случае совпадения и отключения слушателя, и FALSE – если не было найдено заданного слушателя.

 

10) detachAggregate

detachAggregate(ListenerAggregate $aggregate)

Перебирает всех слушателей всех событий, а так же прикрепленных слушателей и отключает их.

Возвращает булевое значение. TRUE в случае совпадения и отключения слушателя, и FALSE – если не было найдено заданного слушателя.

 

11) getEvents

getEvents()

Возвращает имена всех событий у которых есть слушатели.

 

12) getListeners

getListeners(string $event)

Возвращает экземпляр Zend\Stdlib\PriorityQueue всех слушателей, прикрепленных к событию $event.

 

13) clearListeners

clearListeners(string $event)

Удаляет всех слушателей, прикрепленных к $event

 

14) prepareArgs

prepareArgs(array $args)

Создает ArrayObject из заданного $args. Может быть полезно, в случае, когда необходимо изменить аргументы слушателя, и более поздние слушатели смогли увидеть эти изменения.

Примеры

Изменение аргументов (Modifying Arguments)

Может быть полезно, в случае, когда необходимо изменить аргументы слушателя, и более поздние слушатели смогли увидеть эти изменения.

Например, нужно  обеспечить предварительную фильтрацию даты, которая приходит в виде строки и потом конвертируется в аргумент DateTime.

Для этого нужно передать аргументы в prepareArgs(), и передать этот новый объект при возникновении события. Потом у Вас будет возможность вернуть это значение в метод.

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;
    }
    if (!isset($values['date'])) {
        $values['date'] = new DateTime('now');
        return;
    }
    $values['date'] = new Datetime($values['date']);
});
 
$v->inject(array(
    'date' => '2011-08-10 15:30:29',
));

Короткое замыкание (Short Circuiting)

Один из распространенных вариантов использования событий, это срабатывание слушателя кода не задано процесс дальнейшей обработки или пока не вернется определенное значение. Например, если событие возвращает объект Response, то выполнение останавливается.

$listener = function($e) {
    // do some work
 
    // Stop propagation and return a response
    $e->stopPropagation(true);
    return $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__, $this, $argv, function($v) {
            return ($v instanceof Response);
        });
    }
}

Возможно, будет ситуация, когда нужно использовать возвращаемое значение для остановки выполнения или каким то другим способом. Оба метода trigger() и triggerUntil() возвращают экземпляр ResponseCollection. Вызовете метод stopped() этого экземпляра для остановки или метод last() для получения возвращаемого значения из последнего выполненного слушателя:

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__, $this, $argv, function($v) {
            return ($v instanceof Response);
        });
 
        // Test if execution was halted, and return last result:
        if ($results->stopped()) {
            return $results->last();
        }
 
        // continue...
    }
}

Назначение приоритета для слушателей (Assigning Priority to Listeners)

Один из удачных примеров реализации EventManager – это приминение его для кэширования системы. Проверка кеша и обновление его по исполнению необходимых действий.

Третий аргумент в attach() – устанавливает приоритет. Чем выше его значение, тем раньше выполниться слушатель и наоборот. По умолчанию «1»,  а слушатели будут срабатывать по порядку их размещения(создания).

Таким образом, для реализации системы кэширования, нужно вызвать событие в начале метода и конце. В начале метода указываем ранее событие, а в конце – позднее. Вот класс, которых мы хотим закешировать:

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);
    }
}

Теперь создадим ListenerAggregateInterface, который поможет нам кешировать:

use Zend\CacheCache;
use Zend\EventManager\EventCollection;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\EventManager\EventInterface;
 
class CacheListener implements ListenerAggregateInterface
{
    protected $cache;
 
    protected $listeners = array();
 
    public function __construct(Cache $cache)
    {
        $this->cache = $cache; 
    }
 
    public function attach(EventCollection $events)
    {
        $this->listeners[] = $events->attach('get.pre', array($this, 'load'), 100);
        $this->listeners[] = $events->attach('get.post', array($this, 'save'), -100);
    }
 
    public function detach(EventManagerInterface $events)
    {
        foreach ($this->listeners as $index => $listener) {
            if ($events->detach($listener)) {
                unset($this->listeners[$index]);
            }
        }
    }
 
    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);
    }
}

Теперь можем прикрепить совокупность (aggregate) к экзепляру:

$value         = new SomeValueObject();
$cacheListener = new CacheListener($cache);
$value->getEventManager()->attachAggregate($cacheListener);
Теперь, пр  вызове мето/pcolor:#004000color:#009900да get(), при наличии кешируемого содержимого оно будет возвращено сразу. Если же нету, то запустится сборщик и при окончании выполнения метда запишет кеш.
Автор статьи: DuB