πŸ“œ ⬆️ ⬇️

Event Manager with Template Filtering

Recently, I have a need for a simple and functional event manager. After a brief search on Packagist, I found an Evenement package that almost completely matched my requirements. But still he did not qualify due to two parameters:

Of course, I decided to finish and brush the library "for myself."

Event generation by pattern


I needed the ability to generate the necessary events using a template, whose names are hierarchical keys ( foo.bar.baz ).
For example, for such a list of events:

It is necessary to generate all events ending in "event". Or starting with β€œyet” and ending with β€œevent”, and it doesn't matter what is in the middle.

Eventable


After some reflection, I began to implement the library based on the earlier Evenement.

Event Manager

Thinking about the interface, I looked at jQuery and its methods of working with events: on() , one() , off() , trigger() . I liked this approach mostly because of their brevity and brevity.
')
The result is the following interface:
 Dispatcher { public Dispatcher on(string $event, callable $listener) public Dispatcher once(string $event, callable $listener) public Dispatcher off([string $event [, callable $listener ]]) public Dispatcher trigger(string $event [, array $args ]) public Dispatcher fire(string $event [, array $args ]) } 

So, the off() method can take two parameters, and then the specific handler of the specified event will be deleted. One parameter - in this case all event handlers will be deleted. Or do not take any parameters, which means deleting all events and handlers subscribed to them.

trigger() accepts an event key template, and spawns all matching events.
fire() in turn, generates one specifically specified event.

If the handler must be executed once, it is hung on the event with the once() method

Dispatcher.php
 namespace Yowsa\Eventable; class Dispatcher { protected $events = []; public function on($event, callable $listener) { if (!KeysResolver::isValidKey($event)) { throw new \InvalidArgumentException('Invalid event name given'); } if (!isset($this->events[$event])) { $this->events[$event] = []; } $this->events[$event][] = $listener; return $this; } public function once($event, callable $listener) { $onceClosure = function () use (&$onceClosure, $event, $listener) { $this->off($event, $onceClosure); call_user_func_array($listener, func_get_args()); }; $this->on($event, $onceClosure); return $this; } public function off($event = null, callable $listener = null) { if (empty($event)) { $this->events = []; } elseif (empty($listener)) { $this->events[$event] = []; } elseif (!empty($this->events[$event])) { $index = array_search($listener, $this->events[$event], true); if (false !== $index) { unset($this->events[$event][$index]); } } return $this; } public function trigger($event, array $args = []) { $matchedEvents = KeysResolver::filterKeys($event, array_keys($this->events)); if (!empty($matchedEvents)) { if (is_array($matchedEvents)) { foreach ($matchedEvents as $eventName) { $this->fire($eventName, $args); } } else { $this->fire($matchedEvents, $args); } } return $this; } public function fire($event, array $args = []) { foreach ($this->events[$event] as $listener) { call_user_func_array($listener, $args); } return $this; } } 


Parsing keys

Half of the work is done - the dispatcher is implemented and working. The next step is to add event filtering by pattern.
Templates are all the same keys, but with labels for filtering:

For the application.user.signin.error key, you can create the following valid patterns:

To implement this filtering, it took three methods:
 KeysResolver { public static int isValidKey(string $key) public static string getKeyRegexPattern(string $key) public static mixed filterKeys(string $pattern [, array $keys ]) } 


Nothing military: validating a key, converting a pattern into a regular expression, and filtering an array of keys.
KeysResolver.php
 namespace Yowsa\Eventable; class KeysResolver { public static function isValidKey($key) { return preg_match('/^(([\w\d\-]+)\.?)+[^\.]$/', $key); } public static function getKeyRegexPattern($key) { $pattern = ('*' === $key) ? '([^\.]+)' : (('**' === $key) ? '(.*)' : str_replace( array('\*\*', '\*'), array('(.+)', '([^.]*)'), preg_quote($key) ) ); return '/^' . $pattern . '$/i'; } public static function filterKeys($pattern, array $keys = array()) { $matched = preg_grep(self::getKeyRegexPattern($pattern), $keys); if (empty($matched)) { return null; } if (1 === count($matched)) { return array_shift($matched); } return array_values($matched); } } 


The whole package fits into two simple classes, easy to test and decorated with the composer package.

Does it work


To demonstrate how the Eventable works and when it can be useful, here’s a simple example.
 require_once __DIR__ . '/../vendor/autoload.php'; $dispatcher = new Yowsa\Eventable\Dispatcher(); $teacher = 'Mrs. Teacher'; $children = ['Mildred', 'Nicholas', 'Kevin', 'Bobby', 'Anna', 'Kelly', 'Howard', 'Christopher', 'Maria', 'Alan']; // teacher comes in the classroom // and children welcome her once $dispatcher->once('teacher.comes', function($teacher) use ($children) { foreach ($children as $kid) { printf("%-12s- Hello, %s!\n", $kid, $teacher); } }); // every kid answers to teacher once foreach ($children as $kid) { $dispatcher->once("children.{$kid}.says", function() use ($kid) { echo "Hi {$kid}!\n"; }); } // boddy cannot stop to talk $dispatcher->on('children.Bobby.says', function() { echo "\tBobby: I want pee\n"; }); // trigger events echo "{$teacher} is entering the classroom.\n\n"; $dispatcher->trigger('teacher.comes', [$teacher]); echo "\n\n{$teacher} welcomes everyone personally\n\n"; $dispatcher->trigger('children.*.says'); for ($i = 0; $i < 5; $i++) { $dispatcher->trigger('children.Bobby.says'); } 

Result
 Mrs. Teacher is entering the classroom. Mildred β€” Hello, Mrs. Teacher! Nicholas β€” Hello, Mrs. Teacher! Kevin β€” Hello, Mrs. Teacher! Bobby β€” Hello, Mrs. Teacher! Anna β€” Hello, Mrs. Teacher! Kelly β€” Hello, Mrs. Teacher! Howard β€” Hello, Mrs. Teacher! Christopher β€” Hello, Mrs. Teacher! Maria β€” Hello, Mrs. Teacher! Alan β€” Hello, Mrs. Teacher! Mrs. Teacher welcomes everyone personally Hi Mildred! Hi Nicholas! Hi Kevin! Hi Bobby! Bobby: I want pee Hi Anna! Hi Kelly! Hi Howard! Hi Christopher! Hi Maria! Hi Alan! Bobby: I want pee Bobby: I want pee Bobby: I want pee Bobby: I want pee Bobby: I want pee 


Perhaps useful links

Inspired by:

Happened:

Source: https://habr.com/ru/post/202234/


All Articles