martes, 20 de enero de 2015

Eventos

Los eventos permiten introducir código personalizado en el código existente en ciertos puntos de ejecución. Se puede adjuntar código personalizado para un evento de manera que cuando se activa el evento, el código sea ejecutado automáticamente. Por ejemplo, un objeto de anuncio publicitario puede desencadenar un evento messageSent cuando se envía con éxito un mensaje. Si deseamos realizar un seguimiento de los mensajes que se envían con éxito, podríamos simplemente adjuntar el código de seguimiento al evento messageSent.

Yii introduce una clase base llamada yii\base\Component para apoyar eventos. Si una clase necesita disparar eventos, debe extenderse desde yii\base\Component, o desde una clase hija.

Controladores de eventos

Un controlador de eventos es una retroalimentación PHP que se ejecuta cuando el evento que se adjunta se dispara. Puede utilizarse cualquiera de las siguientes retroalimentaciones:

  • una función global PHP especificada como una cadena (sin paréntesis), por ejemplo, 'trim';
  • un método de objeto especificado como una arreglo de un objeto y un nombre de método como una cadena (sin paréntesis), por ejemplo, [$object, 'methodName'];
  • un método de clase estática especificado como un arreglo de un nombre de clase y un nombre de método como una cadena (sin paréntesis), por ejemplo, [$class, 'methodName'] ;
  • una función anónima, por ejemplo, function ($event) { ... } 

La firma de un controlador de eventos es:

function ($event) {
    // $event es un objeto de yii\base\Event o una clase hija
}

A través del parámetro $event, un controlador de eventos puede obtener la siguiente información sobre el evento que ocurrió:

nombre del evento
evento remitente : el objeto cuyo método trigger() fue llamado
datos personalizados: los datos que se proporcionan al adjuntar el controlador de eventos (que se explica a continuación)

Adjuntando controladores de eventos

Se puede adjuntar un controlador de eventos llamando al método yii\base\Component::on(). Por ejemplo:

$foo = new Foo;
// éste controlador es una función global
$foo->on(Foo::EVENT_HELLO'function_name');
// éste controlador es un método de objeto
$foo->on(Foo::EVENT_HELLO, [$object'methodName']);
// éste controlador es un método estático de una clase
$foo->on(Foo::EVENT_HELLO, ['app\components\Bar''methodName']);
// éste controlador es  una función anónima
$foo->on(Foo::EVENT_HELLO, function ($event) {
    // lógica del controlador de eventos
});

También se pueden adjuntar eventos a través de configuraciones. En una entrada futura lo revisaremos.

Al adjuntar un controlador de eventos, se puede proporcionar datos adicionales como tercer parámetro a yii\base\Component::on(). Los datos estarán disponibles al controlador cuando se active el evento y se llamará al controlador. Por ejemplo:

// El siguiente código despliega "abc" cuando el evento es disparado
// porque $event->data contiene los datos del tercer parámetro de "on"$foo->on(Foo::EVENT_HELLO'function_name''abc');

function function_name($event) {
    echo $event->data;
}

Orden de los controladores de eventos

Se puede adjuntar uno o más controladores a un único evento. Cuando se activa un evento, los controladores adjuntos serán llamados en el orden en que se adjuntaron al evento. Si un controlador tiene que parar la invocación de los controladores que le siguen, puede establecer la propiedad yii\base\Event::$handled del parámetro $event a true:

$foo->on(Foo::EVENT_HELLO, function ($event) {
    $event->handled true;
});

Por defecto, un controlador recién adjunto se anexa a la cola de controladores existente para el evento. Como resultado, el controlador será llamado en el último lugar cuando se activa el evento. Para insertar el nuevo controlador en el comienzo de la cola de controladores para que sea llamado primero, puede llamarse a yii\base\Component::on(), pasando false en el cuarto parámetro de $append.

$foo->on(Foo::EVENT_HELLO, function ($event) {
    // ...}, $datafalse);

Activación de eventos

Los eventos se activan o se disparan mediante una llamada al método yii\base\Component::trigger(). El método requiere un nombre de evento, y opcionalmente un objeto evento que describa los parámetros que se pasa a los controladores de eventos. Por ejemplo:

namespace app\components;

use yii\base\Component;
use yii\base\Event;

class Foo extends Component{
    const EVENT_HELLO 'hola';

    public function bar()
    {
        $this->trigger(self::EVENT_HELLO);
    }
}

Con el código anterior, todas las llamadas a bar() activarán el evento hola.

Consejo: Se recomienda el uso de constantes de clase para representar nombres de eventos. En el ejemplo anterior, la constante de EVENT_HELLO representa el evento hola. Este enfoque tiene tres ventajas. En primer lugar, evita errores tipográficos. En segundo lugar, se puede hacer eventos reconocibles para soporte a IDE auto-completar. En tercer lugar, podemos decir qué eventos se apoyan en una clase con tan sólo observar sus constantes declaraciones.

A veces, cuando se dispara un evento es posible que se desee transmitir información adicional a los controladores de eventos. Por ejemplo, un anuncio publicitario puede querer pasar la información del mensaje al controlador del evento messageSent para que entre controladores puedan conocer los detalles de los mensajes enviados. Para ello, se puede proporcionar un objeto evento como el segundo parámetro del método yii\base\Component::trigger(). El objeto evento debe ser una instancia de la clase yii\base\Event  o una clase hija. Por ejemplo:

namespace app\components;

use yii\base\Component;
use yii\base\Event;

class MessageEvent extends Event{
    public $message;
}

class Mailer extends Component{
    const EVENT_MESSAGE_SENT 'messageSent';

    public function send($message)
    {
        // ...enviando $message...

        $event = new MessageEvent;
        $event->message $message;
        $this->trigger(self::EVENT_MESSAGE_SENT$event);
    }
}

Cuando el método yii\base\Component::trigger() es llamado, se llamará a todos los controladores del evento mencionado.

Exclusión de controladores de eventos

Para separar un controlador de un evento, utilizamos el método yii\base\Component::off(). Por ejemplo:

// el controlador en una función global
$foo->off(Foo::EVENT_HELLO'function_name');
// el controlador en un método de objeto
$foo->off(Foo::EVENT_HELLO, [$object'methodName']);
// el controlador es un método estático de una clase
$foo->off(Foo::EVENT_HELLO, ['app\components\Bar''methodName']);
// el controlador es una función anónima
$foo->off(Foo::EVENT_HELLO$anonymousFunction);

Nótese que en general, no se debe tratar de separar una función anónima hasta almacenarla en algún lugar cuando es asociada al evento. En el ejemplo anterior, se asume que la función anónima se almacena como una variable $anonymousFunction.

Para separar todos los controladores de un evento, simplemente hay que llamar a yii\base\Component::off()  sin el segundo parámetro:

$foo->off(Foo::EVENT_HELLO);

Controladores de eventos a Nivel de Clase (Class-Level)

Hasta el momento, se ha descrito cómo adjuntar los controladores de eventos a nivel de una instancia. Sin embargo, algunas veces necesitamos que un evento se dispare por cada instancia de una clase, y no solo por una instancia específica. En lugar de adjunta un controlador de eventos por cada instancia, podemos adjuntar el controlador a nivel de clase llamando al método estático yii\base\Event::on().

Por ejemplo, un objeto Registro Activo, disparará un evento EVENT_AFTER_INSERT cada vez que se inserte un nuevo registro en la base de datos. Si lo que queremos es realizar un seguimiento a cada una de las inserciones realizadas por el Registro Activo, se puede utilizar el siguiente código:

use Yii;
use yii\base\Event;
use yii\db\ActiveRecord;
Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
    Yii::trace(get_class($event->sender) . ' es insertado');
});

El controlador de eventos será invocado cuando una instancia del Registro Activo, o una de sus clases hijas, disparen el evento EVENT_AFTER_INSERT. En el controlador, se puede obtener el objeto que disparó el evento a través de $event->sender.

Cuando un objeto dispara un evento, primero se llama al controlador a nivel de instancia, seguido del controlador a nivel de clase.

Se puede disparar un evento a nivel de clase llamando al método estático yii\base\Event::trigger(). Un evento a nivel de clase no está asociado con un objeto en particular. Como resultado, causará sólo la invocación de los controladores de eventos de nivel de clase. Por ejemplo:

use yii\base\Event;
Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) {
    echo $event->sender;  // despliega "app\models\Foo"
});
Event::trigger(Foo::className(), Foo::EVENT_HELLO);

Nótese que en este caso, $event->sender se refiere al nombre de la clase que activa el evento en lugar de una instancia de objeto.

Nota: Debido a que un controlador a nivel de clase responderá a un evento disparado por cualquier instancia de esa clase o, a su vez de una clase hija, se debe utilizar con cuidado, especialmente si la clase es una clase base de bajo nivel, como yii\base\Object.

Para separar un controlador a nivel de clase se debe llamar a yii\base\Event::off(). Por ejemplo:

// separando $handler
Event::off(Foo::className(), Foo::EVENT_HELLO$handler);
// separando todos los controladores de Foo::EVENT_HELLO
Event::off(Foo::className(), Foo::EVENT_HELLO);

Eventos globales

Yii soporta la llamada a un evento global, que en realidad es un truco basado en el mecanismo de eventos descrito anteriormente. El evento global requiere un único acceso global, tal como la instancia de la aplicación en sí misma.

Para crear un evento global, un evento debe enviar la llamada en evento trigger() único para disparar el evento, en lugar de llamar a su propio método trigger(). De manera similar, los controladores deben añadirse al evento único, por ejemplo:

use Yii;
use yii\base\Event;
use app\components\Foo;
Yii::$app->on('bar', function ($event) {
    echo get_class($event->sender);  // despliega "app\components\Foo"
});
Yii::$app->trigger('bar', new Event(['sender' => new Foo]));

Una ventaja de utilizar los eventos mundiales es que no se necesita de un objeto cuando se adjunta un controlador al evento que se desencadena por por parte del objeto. En su lugar, el controlador adjunto y el evento de activación son completados a través del acceso global (por ejemplo, la instancia de la aplicación).

Sin embargo, debido a que el espacio de nombres (namespace) de los eventos globales es compartido por todas las partes, se deberá nombrar a los eventos globales sabiamente, como la introducción de algún tipo de espacio de nombres adecuado (por ejemplo, "frontend.mail.sent", "backend.mail.sent").


También te puede interesar:
Configuraciones
Comportamientos (Behaviors)
Propiedades
Componentes



No hay comentarios.:

Publicar un comentario

Nota: sólo los miembros de este blog pueden publicar comentarios.