Tabla de contenidos
Zend_EventManager es un componente diseñado para los siguientes casos
de uso:
Implementar patrones simples de sujeto/observador.
Implementar diseños orientados a aspectos.
Implementar arquitecturas dirigidas por eventos.
La arquitectura básica le permite adjuntar y separar escuchadores a eventos con nombre, tanto por instancia como de forma estática; disparar eventos; e interrumpir la ejecución de los escuchadores.
Normalmente, se compone una instancia de Zend_EventManager_EventManager en una clase.
class Foo
{
protected $events;
public function events(Zend_EventManager_EventCollection $events = null)
{
if (null !== $events) {
$this->events = $events;
} elseif (null === $this->events) {
$this->events = new Zend_EventManager_EventManager(__CLASS__);
}
return $this->events;
}
}
Lo anterior permite a los usuarios acceder a la instancia de EventManager, o
restablecerla con una nueva instancia; si no existe ninguna, se instanciará
de forma perezosa bajo demanda.
Un EventManager solo resulta interesante si dispara algún
evento. El disparo básico toma tres argumentos: el nombre del evento, que suele ser el
nombre de la función/método actual; el "contexto", que suele ser la instancia del objeto
actual; y los argumentos, que suelen ser los argumentos proporcionados a la función/método
actual.
class Foo
{
// ... assume events definition from above
public function bar($baz, $bat = null)
{
$params = compact('baz', 'bat');
$this->events()->trigger(__FUNCTION__, $this, $params);
}
}
A su vez, disparar eventos solo resulta interesante si algo está escuchando el evento.
Los escuchadores se adjuntan al EventManager, especificando un evento
con nombre y el callback a notificar. El callback recibe un objeto Zend_EventManager_Event,
que dispone de accesores para recuperar el nombre del evento, el contexto y los parámetros.
Añadamos un escuchador y disparemos el evento.
$log = Zend_Log::factory($someConfig);
$foo = new Foo();
$foo->events()->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"}"
Tenga en cuenta que el segundo argumento de attach() es cualquier callback válido;
se muestra una función anónima en el ejemplo para mantener el ejemplo
autocontenido. Sin embargo, también podría utilizar un nombre de función válido, un functor, una
cadena que haga referencia a un método estático, o un callback de tipo array con un método estático
con nombre o un método de instancia. De nuevo, cualquier callback de PHP es válido.
En ocasiones puede que quiera especificar escuchadores sin tener aún una instancia de objeto de la
clase que compone un EventManager. El
Zend_EventManager_StaticEventManager le permite hacer esto. La llamada a
attach es idéntica a la del EventManager,
pero espera un parámetro adicional al principio: una instancia con nombre. ¿Recuerda el
ejemplo de composición de un EventManager, cómo le pasamos
__CLASS__? Ese valor, o cualquier cadena que proporcione en un array al
constructor, puede utilizarse para identificar una instancia al usar el
StaticEventManager. Como ejemplo, podríamos cambiar el ejemplo
anterior para adjuntar de forma estática:
$log = Zend_Log::factory($someConfig);
$events = Zend_EventManager_StaticEventManager::getInstance();
$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();
// 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"}"
El EventManager también ofrece la posibilidad de separar escuchadores,
cortocircuitar la ejecución de un evento ya sea desde dentro de un escuchador o comprobando los valores
de retorno de los escuchadores, comprobar y recorrer en bucle los resultados devueltos por los escuchadores, priorizar
escuchadores, y más. Muchas de estas características se detallan en los ejemplos.
A veces querrá adjuntar el mismo escuchador a muchos eventos o a todos los eventos de
una instancia dada -- o incluso, con el gestor estático, a muchos contextos y muchos
eventos. El componente EventManager permite esto.
Ejemplo 31.1. Adjuntar a muchos eventos a la vez
$events = new Zend_EventManager_EventManager();
$events->attach(array('these', 'are', 'event', 'names'), $callback);
Tenga en cuenta que si especifica una prioridad, esa prioridad se usará para todos los eventos especificados.
Ejemplo 31.2. Adjuntar usando el comodín
$events = new Zend_EventManager_EventManager();
$events->attach('*', $callback);
Tenga en cuenta que si especifica una prioridad, esa prioridad se usará para este escuchador en cualquier evento disparado.
Lo anterior especifica que cualquier evento disparado resultará en la notificación de este escuchador en particular.
Ejemplo 31.3. Adjuntar a muchos eventos a la vez mediante el StaticEventManager
$events = Zend_EventManager_StaticEventManager::getInstance();
// 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);
Tenga en cuenta que si especifica una prioridad, esa prioridad se usará para todos los eventos especificados.
Ejemplo 31.4. Adjuntar a muchos eventos a la vez mediante el StaticEventManager
$events = Zend_EventManager_StaticEventManager::getInstance();
// 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);
Tenga en cuenta que si especifica una prioridad, esa prioridad se usará para todos los eventos especificados.
Lo anterior especifica que para los contextos "foo" y "bar", el escuchador especificado será notificado para cualquier evento que disparen.
Opciones de Zend_EventManager_EventManager
- identifier
Una cadena o array de cadenas a las que la instancia de
EventManagerdada puede responder cuando se accede a través de elStaticEventManager.- event_class
El nombre de una clase alternativa de
Zend_EventManager_Eventpara usar al representar los eventos pasados a los escuchadores.- static_connections
Una instancia de
Zend_EventManager_StaticEventCollectionpara usar al disparar eventos. Por defecto, se usará la instancia global deZend_EventManager_StaticEventManager, pero esto puede anularse pasando un valor a este método. Un valornullevitará que la instancia dispare más escuchadores adjuntados de forma estática.
- __construct
-
__construct(null|string|int $identifier);Construye una nueva instancia de
EventManager, usando el identificador dado, si se proporciona, con fines de adjunción estática. - setEventClass
-
setEventClass(string $class);Proporciona el nombre de una clase alternativa de
Zend_EventManager_Eventpara usar al crear eventos que se pasarán a los escuchadores disparados. - setStaticConnections
-
setStaticConnections(Zend_EventManager_StaticEventCollection $connections = null);Una instancia de
Zend_EventManager_StaticEventCollectionpara usar al disparar eventos. Por defecto, se usará la instancia global deZend_EventManager_StaticEventManager, pero esto puede anularse pasando un valor a este método. Un valornullevitará que la instancia dispare más escuchadores adjuntados de forma estática. - getStaticConnections
-
getStaticConnections();Devuelve la instancia de
Zend_EventManager_StaticEventCollectionactualmente adjunta, recuperando de forma perezosa la instancia global deZend_EventManager_StaticEventManagersi no hay ninguna adjunta y el uso de escuchadores estáticos no ha sido deshabilitado pasando un valornulla setStaticConnections(). Devuelve un valor booleanofalsesi los escuchadores estáticos están deshabilitados, o una instancia deStaticEventCollectionen caso contrario. - trigger
-
trigger(string $event, mixed $target, mixed $argv, callback $callback);Dispara todos los escuchadores de un evento con nombre. Se recomienda usar el nombre de la función/método actual para
$event, añadiéndole valores como ".pre", ".post", etc. según sea necesario.$contextdebería ser la instancia del objeto actual, o el nombre de la función si no se dispara dentro de un objeto.$paramsnormalmente debería ser un array asociativo o una instancia deArrayAccess; recomendamos usar los parámetros pasados a la función/método (compact()es a menudo útil aquí). Este método también puede recibir un callback y comportarse de la misma manera quetriggerUntil().El método devuelve una instancia de
Zend_EventManager_ResponseCollection, que puede usarse para inspeccionar los valores de retorno de los distintos escuchadores, comprobar si hubo cortocircuito, y más. - triggerUntil
-
triggerUntil(string $event, mixed $context, mixed $argv, callback $callback);Dispara todos los escuchadores de un evento con nombre, igual que trigger(), con la particularidad de que pasa el valor de retorno de cada escuchador a
$callback; si$callbackdevuelve un valor booleanotrue, se interrumpe la ejecución de los escuchadores. Puede comprobar esto usando$result->stopped(). - attach
-
attach(string $event, callback $callback, int $priority);Adjunta
$callbacka la instancia deZend_EventManager_EventManager, escuchando el evento$event. Si se proporciona una$priority, el escuchador se insertará en la pila interna de escuchadores usando esa prioridad; los valores más altos se ejecutan antes. (La prioridad por defecto es "1", y se permiten prioridades negativas.)El método devuelve una instancia de
Zend_Stdlib_CallbackHandler; este valor puede pasarse posteriormente adetach()si se desea. - attachAggregate
-
attachAggregate(string|Zend_EventManager_ListenerAggregate $aggregate);Si se pasa una cadena para
$aggregate, se instancia esa clase. Luego se pasa la instancia deEventManagera$aggregatemediante su métodoattach()para que pueda registrar escuchadores.Se devuelve la instancia de
ListenerAggregate. - detach
-
detach(Zend_Stdlib_CallbackHandler $listener);Recorre todos los escuchadores y separa cualquiera que coincida con
$listenerpara que ya no se disparen.Devuelve un valor booleano
truesi se ha identificado y anulado la suscripción de algún escuchador, y un valor booleanofalseen caso contrario. - detachAggregate
-
detachAggregate(Zend_EventManager_ListenerAggregate $aggregate);Recorre todos los escuchadores de todos los eventos para identificar a los escuchadores representados por el agregado; para todas las coincidencias, los escuchadores se eliminarán.
Devuelve un valor booleano
truesi se ha identificado y anulado la suscripción de algún escuchador, y un valor booleanofalseen caso contrario. - detachAggregate
-
getEvents();Devuelve un array con todos los nombres de eventos que tienen escuchadores adjuntos.
- getListeners
-
getListeners(string $event);Devuelve una instancia de
Zend_Stdlib_PriorityQueuecon todos los escuchadores adjuntos a$event. - clearListeners
-
clearListeners(string $event);Elimina todos los escuchadores adjuntos a
$event. - prepareArgs
-
prepareArgs(array $args);Crea un
ArrayObjecta partir del$argsproporcionado. Esto puede resultar útil si desea que sus escuchadores puedan modificar los argumentos de modo que los escuchadores posteriores o el método que dispara el evento puedan ver los cambios.
Ejemplo 31.5. Modificar argumentos
En ocasiones puede resultar útil permitir que los escuchadores modifiquen los argumentos que reciben, de modo que los escuchadores posteriores o el método invocador reciban esos valores modificados.
Como ejemplo, podría querer pre-filtrar una fecha que sabe que llegará como una
cadena y convertirla en un argumento DateTime.
Para ello, puede pasar sus argumentos a prepareArgs(),
y pasar este nuevo objeto al disparar un evento. Luego recuperará ese valor
de vuelta en su método.
class ValueObject
{
// assume a composed event manager
function inject(array $values)
{
$argv = compact('values');
$argv = $this->events()->prepareArgs($argv);
$this->events()->trigger(__FUNCTION__, $this, $argv);
$date = isset($argv['values']['date']) ? $argv['values']['date'] : new DateTime('now');
// ...
}
}
$v = new ValueObject();
$v->events()->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',
));
Ejemplo 31.6. Cortocircuitar
Un caso de uso común para los eventos es disparar escuchadores hasta que uno indique que no se debe realizar más procesamiento, o hasta que un valor de retorno cumpla criterios específicos. Como ejemplo, si un evento crea un objeto Response, puede que se desee detener la ejecución.
$listener = function($e) {
// do some work
// Stop propagation and return a response
$e->stopPropagation(true);
return $response;
};
Alternativamente, podríamos hacer la comprobación desde el método que dispara el evento.
class Foo implements Dispatchable
{
// assume composed event manager
public function dispatch(Request $request, Response $response = null)
{
$argv = compact('request', 'response');
$results = $this->events()->triggerUntil(__FUNCTION__, $this, $argv, function($v) {
return ($v instanceof Response);
});
}
}
Normalmente, puede que desee devolver el valor que detuvo la ejecución, o usarlo de alguna
manera. Tanto trigger() como
triggerUntil() devuelven una instancia de
Zend_EventManager_ResponseCollection; llame a su método
stopped() para comprobar si la ejecución se detuvo, y al método
last() para recuperar el valor de retorno del último escuchador
ejecutado:
class Foo implements Dispatchable
{
// assume composed event manager
public function dispatch(Request $request, Response $response = null)
{
$argv = compact('request', 'response');
$results = $this->events()->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...
}
}
Ejemplo 31.7. Asignar prioridad a los escuchadores
Un caso de uso para el EventManager es la implementación de sistemas
de caché. Como tal, a menudo se desea comprobar la caché de forma temprana, y guardar en ella de forma tardía.
El tercer argumento de attach() es un valor de prioridad. Cuanto
más alto sea este número, antes se ejecutará ese escuchador; cuanto más bajo sea,
más tarde se ejecutará. El valor por defecto es 1, y los valores se dispararán en el orden
en que se registraron dentro de una prioridad determinada.
Así, para implementar un sistema de caché, nuestro método necesitará disparar un evento al inicio del método así como al final del método. Al inicio del método, queremos un evento que se dispare de forma temprana; al final del método, un evento debería dispararse de forma tardía.
Aquí está la clase en la que queremos la caché:
class SomeValueObject
{
// assume it composes an event manager
public function get($id)
{
$params = compact('id');
$results = $this->events()->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->events()->trigger('get.post', $this, $params);
}
}
Ahora, creemos un ListenerAggregate que pueda manejar
la caché por nosotros:
class CacheListener implements Zend_EventManager_ListenerAggregate
{
protected $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
public function attach(Zend_EventManager_EventCollection $events)
{
$events->attach('get.pre', array($this, 'load'), 100);
$events->attach('get.post', array($this, 'save'), -100);
}
public function load($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($e)
{
$params = $e->getParams();
$content = $params['__RESULT__'];
unset($params['__RESULT__']);
$id = get_class($e->getTarget()) . '-' . json_encode($params);
$this->cache->save($content, $id);
}
}
Podemos entonces adjuntar el agregado a una instancia.
$value = new SomeValueObject(); $cacheListener = new CacheListener($cache); $value->events()->attachAggregate($cacheListener);
Ahora, cuando llamemos a get(), si tenemos una entrada en caché, se
devolverá inmediatamente; si no, se guardará en caché una entrada calculada al completar
el método.