PHP: Event Manager

PHP

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 :)

Komentáře