Event library that implements variations of the mediator and observer patterns.
Contents
- emitting events with any number of arguments
- managing listeners for specific or all events
- ordering listeners by priority
- stopping event propagation
- multiple ways to embed the event system
- PHP 7.1+
The EventEmitter
class maintains a list of listeners and dispatches events
to them.
It is intended to be used as a mediator.
<?php
use Kuria\Event\EventEmitter;
$emitter = new EventEmitter();
EventEmitter
implements ObservableInterface
.
The abstract Observable
class implements the ObservableInterface
using
an inner event emitter.
It is intended to be extended by child classes that will emit their own events:
<?php
use Kuria\Event\Observable;
class MyComponent extends Observable
{
function doSomething()
{
$this->emit('something');
}
}
Alternatively, you can use the ObservableTrait
to achieve the same result:
<?php
use Kuria\Event\EventEmitterPropInterface;
use Kuria\Event\ObservableInterface;
use Kuria\Event\ObservableTrait;
class MyComponent implements ObservableInterface, EventEmitterPropInterface
{
use ObservableTrait;
// ...
}
The following applies to both event emitter and observable as they
both implement the ObservableInterface
.
To register a callback to be called when a specific event occurs, register it
using the on()
method. Any event arguments will be passed directly to it.
<?php
$observable->on('some.event', function ($arg1, $arg2) {
// do something
});
- the callback can stop event propagation by returning
FALSE
- listener priority can be specified using the 3rd argument of
on()
To unregister a callback, call the off()
method with the same callback
(in case of closures this means the same object):
<?php
$observable->off('some.event', $callback); // returns TRUE on success
To register an event listener, use the addListener()
method:
<?php
use Kuria\Event\EventListener;
$observable->addListener(
new EventListener(
'some.event',
function ($arg1, $arg2) {}
)
);
- listener priority can be specified by using the 3rd argument of
the
EventListener
constructor - the callback can stop event propagation by returning
FALSE
To unregister a listener, call the removeListener()
method with the same
event listener object:
<?php
$observable->removeListener($eventListener); // returns TRUE on success
Event subscribers subscribe to a list of events. Each event is usually mapped to one method of the subscriber.
The listeners can be created using the convenient listen()
method
(as shown in the example below) or by manually creating EventListener
instances.
- any callback or method can stop event propagation by returning
FALSE
- listener priority can be specified using 3rd argument of
listen()
or theEventListener
constructor
<?php
use Kuria\Event\EventSubscriber;
class MySubscriber extends EventSubscriber
{
protected function getListeners(): array
{
return [
$this->listen('foo.bar', 'onFooBar'),
$this->listen('lorem.ipsum', 'onLoremIpsum', 10),
$this->listen('dolor.sit', 'onDolorSitA'),
$this->listen('dolor.sit', 'onDolorSitB', 5),
];
}
function onFooBar() { /* do something */ }
function onLoremIpsum() { /* do something */ }
function onDolorSitA() { /* do something */ }
function onDolorSitB() { /* do something */ }
}
$subscriber = new MySubscriber();
Registering the event subscriber:
<?php
$subscriber->subscribeTo($observable);
Unregistering the event subsriber:
<?php
$subscriber->unsubscribeFrom($observable);
Any listener can stop further propagation of the current event by returning FALSE
.
This prevents any other listeners from being invoked.
Listener priority determines the order in which the listeners are invoked:
- listeners with greater priority are invoked sooner
- listeners with lesser priority are invoked later
- if the priorities are equal, the order of invocation is undefined
- priority can be negative
- default priority is
0
To listen to all events, use ObservableInterface::ANY_EVENT
in place
of the event name:
<?php
use Kuria\Event\EventListener;
use Kuria\Event\ObservableInterface;
$observable->on(
ObservableInterface::ANY_EVENT,
function ($event, $arg1, $arg2) {}
);
$observable->addListener(
new EventListener(
ObservableInterface::ANY_EVENT,
function ($event, $arg1, $arg2) {}
)
);
- global listeners are invoked before listeners of specific events
- global listeners get an extra event name argument before the emitted event arguments
- global listeners can also stop event propagation by returning
FALSE
and may have specified listener priority
Events are emitted using the emit()
method.
<?php
$observable->emit('foo');
Any extra arguments will be passed to the listeners.
<?php
$observable->emit('foo', 'hello', 123);
Note
Variable references cannot be emitted directly as an argument. If you need to use references, wrap them in an object or an array.
While the event library itself doesn't require this, it is a good idea to explicitly define possible event names and their arguments somewhere.
The example below defines a FieldEvents
class for this purpose. Constants of this class
are then used in place of event names and their annotations serve as documentation. This also
allows for code-completion.
<?php
use Kuria\Event\Observable;
/**
* @see Field
*/
abstract class FieldEvents
{
/**
* Emitted when field value is about to be changed.
*
* @param Field $field
* @param mixed $oldValue
* @param mixed $newValue
*/
const CHANGE = 'change';
/**
* Emitted when field value is about to be cleared.
*
* @param Field $field
*/
const CLEAR = 'clear';
}
/**
* @see FieldEvents
*/
class Field extends Observable
{
private $name;
private $value;
function __construct(string $name, $value = null)
{
$this->name = $name;
$this->value = $value;
}
function getName(): string
{
return $this->name;
}
function getValue()
{
return $this->value;
}
function setValue($value): void
{
$this->emit(FieldEvents::CHANGE, $this, $this->value, $value);
$this->value = $value;
}
function clear()
{
$this->emit(FieldEvents::CLEAR, $this);
$this->value = null;
}
}
Note
Using @param
annotations on class constants is non-standard, but IDE's dont mind
it and some documentation-generators (such as Doxygen) even display them nicely.
<?php
$field = new Field('username');
$field->on(FieldEvents::CHANGE, function (Field $field, $oldValue, $newValue) {
echo "Field '{$field->getName()}' has been changed from '{$oldValue}' to '{$newValue}'\n";
});
$field->on(FieldEvents::CLEAR, function (Field $field) {
echo "Field '{$field->getName()}' has been cleared\n";
});
$field->setValue('john.smith');
$field->setValue('foo.bar123');
$field->clear();
Output:
Field 'username' has been changed from '' to 'john.smith' Field 'username' has been changed from 'john.smith' to 'foo.bar123' Field 'username' has been cleared