# Eventos ¶

Los eventos permiten inyectar código dentro de otro código existente en ciertos puntos de ejecución. Se pueden adjuntar código personalizado a un evento, cuando se lance (triggered), el código se ejecutará automáticamente. Por ejemplo, un objeto mailer puede lanzar el evento `messageSent` cuando se envía un mensaje correctamente. Si se quiere rastrear el correcto envío del mensaje, se puede, simplemente, añadir un código de seguimiento al evento `messageSent`.

Yii introduce una clase base yii\base\Component para soportar eventos. Si una clase necesita lanzar un evento, este debe extender a yii\base\Component o a una clase hija.

## Gestor de Eventos ¶

Un gestor de eventos es una llamada de retorno PHP (PHP callback) que se ejecuta cuando se lanza el evento al que corresponde. Se puede usar cualquier llamada de retorno de las enumeradas a continuación:

- una función de PHP global especificada como una cadena de texto (sin paréntesis), ej. `'trim'`;
- un método de objeto especificado como un array de un objeto y un nombre de método como una cadena de texto (sin paréntesis), ej. `[$object, 'methodNAme']`;
- un método de clase estático especificado como un array de un nombre de clase y un método como una cadena de texto (sin paréntesis), ej. `[$class, 'methodName']`;
- una función anónima, ej. `function ($event) { ... }`.

La firma de un gestor de eventos es:

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

Un gestor de eventos puede obtener la siguiente información acerca de un evento ya sucedido mediante el parámetro `$event`:

- nombre del evento
- evento enviando: el objeto desde el que se ha ejecutado `trigger()`
- custom data: los datos que se proporcionan al adjuntar el gestor de eventos (se explicará más adelante)

## Añadir Gestores de Eventos ¶

Se puede añadir un gestor a un evento llamando al método yii\base\Component::on(). Por ejemplo:

    $foo = new Foo;

    // este gestor es una función global
    $foo->on(Foo::EVENT_HELLO, 'function_name');

    // este gestor es un método de objeto
    $foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);

    // este gestor es un método de clase estática
    $foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);

    // este gestor es una función anónima
    $foo->on(Foo::EVENT_HELLO, function ($event) {
        // event handling logic
    });

También se pueden adjuntar gestores de eventos mediante configuraciones. Se pueden encontrar más de talles en la sección Configuraciones.

Cuando se adjunta un gestor de eventos, se pueden proporcionar datos adicionales como tercer parámetro de yii\base\Component::on(). El gestor podrá acceder a los datos cuando se lance el evento y se ejecute el gestor. Por ejemplo:

    // El siguiente código muestra "abc" cuando se lanza el evento
    // ya que $event->data contiene los datos enviados en el tercer parámetro de "on"
    $foo->on(Foo::EVENT_HELLO, 'function_name', 'abc');

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

## Ordenación de Gestores de Eventos ¶

Se puede adjuntar uno o más gestores a un único evento. Cuando se lanza un evento, se ejecutarán los gestores adjuntos en el orden que se hayan añadido al evento. Si un gestor necesita parar la invocación de los gestores que le siguen, se puede establecer la propiedad yii\base\Event::$handled del parámetro `$event` para que sea `true`:

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

De forma predeterminada, cada nuevo gestor añadido se pone a la cola de la lista de gestores del evento. Por lo tanto, el gestor se ejecutará en el último lugar cuando se lance el evento. Para insertar un nuevo gestor al principio de la cola de gestores para que sea ejecutado primero, se debe llamar a yii\base\Component::on(), pasando al cuarto parámetro `$append` el valor `false`:

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

## Lanzamiento de Eventos ¶

Los eventos se lanzan llamando al método yii\base\Component::trigger(). El método requiere un nombre de evento, y de forma opcional un objeto de evento que describa los parámetros que se enviarán a los gestores de eventos. Por ejemplo:

    namespace app\components;

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

    class Foo extends Component
    {
        const EVENT_HELLO = 'hello';

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

Con el código anterior, cada llamada a `bar()` lanzará un evento llamado `hello`

> Consejo: Se recomienda usar las constantes de clase para representar nombres de eventos. En el anterior ejemplo, la constante `EVENT_HELLO` representa el evento `hello`. Este enfoque proporciona tres beneficios. Primero, previene errores tipográficos. Segundo, puede hacer que los IDEs reconozcan los eventos en las funciones de auto-completado. Tercero, se puede ver que eventos soporta una clase simplemente revisando la declaración de constantes.

A veces cuando se lanza un evento se puede querer pasar información adicional al gestor de eventos. Por ejemplo, un mailer puede querer enviar la información del mensaje para que los gestores del evento `messageSent` para que los gestores puedan saber las particularidades del mensaje enviado. Para hacerlo, se puede proporcionar un objeto de tipo evento como segundo parámetro al método yii\base\Component::trigger(). El objeto de tipo evento debe ser una instancia de la clase yii\base\Event o de su 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 se lanza el método yii\base\Component::trigger(), se ejecutarán todos los gestores adjuntos al evento.

## Desadjuntar Gestores de Evento ¶

Para desadjuntar un gestor de un evento, se puede ejecutar el método yii\base\Component::off(). Por ejemplo:

    // el gestor es una función global
    $foo->off(Foo::EVENT_HELLO, 'function_name');

    // el gestor es un método de objeto
    $foo->off(Foo::EVENT_HELLO, [$object, 'methodName']);

    // el gestor es un método estático de clase
    $foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);

    // el gestor es una función anónima
    $foo->off(Foo::EVENT_HELLO, $anonymousFunction);

Tenga en cuenta que en general no se debe intentar desadjuntar las funciones anónimas a no ser que se almacene donde se ha adjuntado al evento. En el anterior ejemplo, se asume que la función anónima se almacena como variable `$anonymousFunction`.

Para desadjuntar TODOS los gestores de un evento, se puede llamar yii\base\Component::off() sin el segundo parámetro:

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

## Nivel de Clase (Class-Level) Gestores de Eventos ¶

En las subsecciones anteriores se ha descrito como adjuntar un gestor a un evento a nivel de instancia. A veces, se puede querer que un gestor responda todos los eventos de todos las instancias de una clase en lugar de una instancia especifica. En lugar de adjuntar un gestor de eventos a una instancia, se puede adjuntar un gestor a nivel de clase llamando al método estático yii\base\Event::on().

Por ejemplo, un objeto de tipo Active Record lanzará un evento EVENT_AFTER_INSERT cada vez que inserte un nuevo registro en la base de datos. Para poder registrar las inserciones efectuadas por todos los objetos Active Record, se puede usar 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::debug(get_class($event->sender) . ' is inserted');
    });

Se invocará al gestor de eventos cada vez que una instancia de ActiveRecord, o de uno de sus clases hijas, lance un evento de tipo EVENT_AFTER_INSERT. Se puede obtener el objeto que ha lanzado el evento mediante `$event->sender` en el gestor.

Cuando un objeto lanza un evento, primero llamará los gestores a nivel de instancia, y a continuación los gestores a nivel de clase.

Se puede lanzar un evento de tipo nivel de clase llamando al método estático yii\base\Event::trigger(). Un evento de nivel de clase no se asocia a un objeto en particular. Como resultado, esto provocará solamente la invocación de los gestores de eventos a nivel de clase.

    use yii\base\Event;

    Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) {
        var_dump($event->sender);  // displays "null"
    });

    Event::trigger(Foo::className(), Foo::EVENT_HELLO);

Tenga en cuenta que en este caso, el `$event->sender` hace referencia al nombre de la clase que lanza el evento en lugar de a la instancia del objeto.

> Nota: Debido a que los gestores a nivel de clase responderán a los eventos lanzados por cualquier instancia de la clase, o cualquier clase hija, se debe usar con cuidado, especialmente en las clases de bajo nivel (low-level), tales como yii\base\BaseObject.

Para desadjuntar un gestor de eventos a nivel de clase, se tiene que llamar a yii\base\Event::off(). Por ejemplo:

    // desadjunta $handler
    Event::off(Foo::className(), Foo::EVENT_HELLO, $handler);

    // desadjunta todos los gestores de Foo::EVENT_HELLO
    Event::off(Foo::className(), Foo::EVENT_HELLO);

## Eventos Globales ¶

Yii soporta los llamados eventos globales, que en realidad es un truco basado en el gestor de eventos descrito anteriormente. El evento global requiere un Singleton globalmente accesible, tal como la instancia de aplicación en si misma.

Para crear un evento global, un evento remitente (event sender) llama al método `trigger()` del Singleton para lanzar el evento, en lugar de llamar al propio método `trigger()` del remitente. De forma similar, los gestores de eventos se adjuntan al evento del Singleton. Por ejemplo:

    use Yii;
    use yii\base\Event;
    use app\components\Foo;

    Yii::$app->on('bar', function ($event) {
        echo get_class($event->sender);  // muestra "app\components\Foo"
    });

    Yii::$app->trigger('bar', new Event(['sender' => new Foo]));

Un beneficio de usar eventos globales es que no se necesita un objeto cuando se adjuntan gestores a un evento para que sean lanzados por el objeto. En su lugar, los gestores adjuntos y el lanzamiento de eventos se efectúan en el Singleton (ej. la instancia de la aplicación).

Sin embargo, debido a que los `namespaces` de los eventos globales son compartidos por todas partes, se les deben asignar nombres bien pensados, como puede ser la introducción de algún `namespace` (ej. "frontend.mail.sent", "backend.mail.sent").