POZOR! Článek jsem napsal před více jak rokem, a tudíž už nemusí reflektovat můj nynější názor nebo může být zastaralý.
Další z dlouhého seznamu komponent, které postupně publikuji je Event Manager. De facto se jedná o implementaci návrhu standardu, který vytváří skupina PHP-FIG.
Z principu jde o navázání různých volání na určitou událost. Je to velmi užitečná věc, díky které lze velmi zpřehlednit kód. Uvedu známý případ, kdy máme objednávku a po jejím dokončení se odešle potvrzující e-mail. Místo, abychom e-mail odeslali přímo v kódu, kde se objednávka dokončuje, vyvoláme událost „košík odeslán“. Event Manager se podívá, jaké listenery jsou na tuto událost registrovány a následně se zavolají.
Jde o velmi silný nástroj, který může ale i naopak kód znepřehlednit a to ve chvíli, když se začnete ztrácet, kdy se co volá. Je třeba v tom udržovat pořádek, volit správnou strukturu a pojmenování.
První ze dvou tříd je entita Event, která udržuje informaci o názvu, cílovém objektu, parametrech a v poslední řadě o tom, zda se má přerušit běh volání listenerů.
namespace Gephart\EventManager;
class Event implements EventInterface
{
private $name;
private $targer;
private $params;
private $stop_propagation = false;
public function getName(): string {
return $this->name;
}
public function getTarget() {
return $this->targer;
}
public function getParams(): array {
return $this->params;
}
public function getParam(string $name) {
return !empty($this->params[$name]) ? $this->params[$name] : false;
}
public function setName(string $name) {
$this->name = $name;
}
public function setTarget(object $target = null) {
$this->targer = $target;
}
public function setParams(array $params) {
$this->params = $params;
}
public function stopPropagation(bool $flag) {
$this->stop_propagation = $flag;
}
public function isPropagationStopped(): bool {
return $this->stop_propagation;
}
}
Třída Event Manager v sobě udržuje informace o listenerech, na jakou událost jsou navázány a jakou mají prioritu. Po každém přidání listeneru dojde k jejich seřezeni sestupně podle priority.
namespace Gephart\EventManager;
final class EventManager implements EventManagerInterface
{
private $listeners = [];
public function attach(string $event, callable $callback, int $priority = 0): bool {
$this->detach($event, $callback);
$this->listeners[] = [
"event" => $event,
"callback" => $callback,
"priority" => $priority
];
usort($this->listeners, function ($a, $b){
return $a["priority"] < $b["priority"];
});
return true;
}
public function detach(string $event, callable $callback): bool {
foreach($this->listeners as $key=>$listener) {
if ($listener["event"] == $event && $listener["callback"] == $callback) {
unset($this->listeners[$key]);
return true;
}
}
return false;
}
public function clearListeners(string $event) {
foreach($this->listeners as $key=>$listener) {
if ($listener["event"] == $event) {
unset($this->listeners[$key]);
}
}
}
public function trigger($event, object $target = null, array $argv = []) {
if (is_string($event)) {
$event_name = $event;
$event = new Event();
$event->setName($event_name);
$event->setTarget($target);
$event->setParams($argv);
} elseif ($event instanceof EventInterface) {
$event_name = $event->getName();
} else {
throw new \Exception("EventManager: Param event must be string of instance of EventInterface");
}
$result = false;
foreach($this->listeners as $key=>$listener) {
if ($listener["event"] == $event_name) {
$result = $listener["callback"]($event);
if ($event->isPropagationStopped()) {
return $result;
}
}
}
return $result;
}
}
Připravuji samostatný článek o „best practises“ využítí, a proto zde uvedu pouze jednoduchou ukázku:
$listener1 = function(){echo "Hello";};
$listener2 = function(){echo "World";};
$event_manager->attach("my.event", $listener1, 200);
$event_manager->attach("my.event", $listener2, 100);
$event_manager->trigger("my.event"); // HelloWorld
Závěrem
Kód včetně testu je opět na GitHubu: https://github.com/gephart/event-manager.
Instalovat lze jednoduše přes composer:
composer require gephart/event-manager
Znáte někoho, komu by článek mohl pomoct? Zasdílejte mu ho :)