|
|
|
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 <a name="event-handlers"></a>
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
Un gestor de eventos es una
|
|
|
|
[llamada de retorno PHP (PHP callback)](http://php.net/manual/es/language.types.callable.php) 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:
|
|
|
|
|
|
|
|
```php
|
|
|
|
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`:
|
|
|
|
|
|
|
|
- [[yii\base\Event::name|nombre del evento]]
|
|
|
|
- [[yii\base\Event::sender|evento enviando]]: el objeto desde el que se ha ejecutado `trigger()`
|
|
|
|
- [[yii\base\Event::data|custom data]]: los datos que se proporcionan al adjuntar el gestor de eventos
|
|
|
|
(se explicará más adelante)
|
|
|
|
|
|
|
|
|
|
|
|
Añadir Gestores de Eventos <a name="attaching-event-handlers"></a>
|
|
|
|
--------------------------
|
|
|
|
|
|
|
|
Se puede añadir un gestor a un evento llamando al método [[yii\base\Component::on()]]. Por ejemplo:
|
|
|
|
|
|
|
|
```php
|
|
|
|
$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](concept-configurations.md). Se pueden
|
|
|
|
encontrar más de talles en la sección [Configuraciones](concept-configurations.md#configuration-format).
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
```php
|
|
|
|
// 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`:
|
|
|
|
|
|
|
|
```php
|
|
|
|
$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:
|
|
|
|
|
|
|
|
```php
|
|
|
|
$foo->on(Foo::EVENT_HELLO, function ($event) {
|
|
|
|
// ...
|
|
|
|
}, $data, false);
|
|
|
|
```
|
|
|
|
|
|
|
|
Lanzamiento de Eventos <a name="triggering-events"></a>
|
|
|
|
----------------------
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
```php
|
|
|
|
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:
|
|
|
|
|
|
|
|
```php
|
|
|
|
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 <a name="detaching-event-handlers"></a>
|
|
|
|
------------------------------
|
|
|
|
|
|
|
|
Para desadjuntar un gestor de un evento, se puede ejecutar el método [[yii\base\Component::off()]]. Por ejemplo:
|
|
|
|
|
|
|
|
```php
|
|
|
|
// 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:
|
|
|
|
|
|
|
|
```php
|
|
|
|
$foo->off(Foo::EVENT_HELLO);
|
|
|
|
```
|
|
|
|
|
|
|
|
Nivel de Clase (Class-Level) Gestores de Eventos <a name="class-level-event-handlers"></a>
|
|
|
|
------------------------------------------------
|
|
|
|
|
|
|
|
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](db-active-record.md) lanzará un evento
|
|
|
|
[[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|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](db-active-record.md), se puede usar el siguiente código:
|
|
|
|
|
|
|
|
```php
|
|
|
|
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) . ' is inserted');
|
|
|
|
});
|
|
|
|
```
|
|
|
|
|
|
|
|
Se invocará al gestor de eventos cada vez que una instancia de [[yii\db\ActiveRecord|ActiveRecord]], o de uno de sus
|
|
|
|
clases hijas, lance un evento de tipo [[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|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.
|
|
|
|
|
|
|
|
```php
|
|
|
|
use yii\base\Event;
|
|
|
|
|
|
|
|
Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) {
|
|
|
|
echo $event->sender; // displays "app\models\Foo"
|
|
|
|
});
|
|
|
|
|
|
|
|
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\Object]].
|
|
|
|
|
|
|
|
Para desadjuntar un gestor de eventos a nivel de clase, se tiene que llamar a [[yii\base\Event::off()]]. Por ejemplo:
|
|
|
|
|
|
|
|
```php
|
|
|
|
// 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 <a name="global-events"></a>
|
|
|
|
----------------
|
|
|
|
|
|
|
|
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](structure-applications.md) 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:
|
|
|
|
|
|
|
|
```php
|
|
|
|
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").
|