Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Диспетчер событий с фильтрацией по образцу

Anna | 31.05.2014 | нет комментариев

Недавно у меня возникла надобность в простом и функциональном диспетчере событий. Позже непродолжительных поисков на Packagist-е я нашел пакет Evenement, тот, что примерно всецело подходил под мои требования. Но все же отбор он не прошел из-за 2-х параметров:

  • была необходима вероятность порождать события по шаблону;
  • интерфейс библиотеки визуально не понравился.

Конечно же, я принял решение доделать и причесать библиотеку «под себя».

Продукт событий по шаблону

Мне необходима была вероятность с помощью образца порождать надобные события, имена которых представляют собой иерархические ключи (foo.bar.baz).
Скажем, для такого списка событий:

  • some.event
  • another.event
  • yet.another.event
  • something.new

Необходимо породить все события, заканчивающиеся на «event». Либо начинающиеся на «yet» и заканчивающиеся на «event», и не важно, что в середине.

Eventable

Позже маленьких размышлений я принялся к реализации библиотеки, базируясь на ранее обнаруженном Evenement.

Диспетчер событий

Думая над интерфейсом, я посматривал на jQuery и его способы работы с событиями: on()one()off(),trigger(). Такой подход пришелся мне по душе по большей части из-за краткости и лаконичности.

В итоге получился дальнейший интерфейс:

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 ])
}

Так, способ off() может принимать два параметра, и тогда будет удален определенный обработчик указанного события. Один параметр — в этом случае будут удалены все обработчики события. Либо не принимать никаких параметров, что обозначает удаление всех событий и подписанных на них обработчиков.

trigger() принимает образец ключа события, и порождает все подходящие события.
fire() в свою очередь порождает одно, определенно заданное событие.

Если обработчик должен быть исполнен однажды, он вешается на событие способом once()

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;
    }
}
Разбор ключей

Половина работы сделана — диспетчер реализован и работает. Дальнейший шаг — добавить фильтрацию событий по шаблону.
Образцы представляют собой все те же ключи, но с метками для фильтрации:

  • * — один секция, до разделителя;
  • ** — любое число секций.

Для ключа application.user.signin.error дозволено составить такие правильные образцы:

  • application.**.error
  • **.error
  • application.user.*.error
  • application.user.**

Для реализации такой фильтрации, потребовалось три способа:

KeysResolver {
    public static int isValidKey(string $key)
    public static string getKeyRegexPattern(string $key)
    public static mixed filterKeys(string $pattern [, array $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);
    }
}

Каждый пакет вмещается в два примитивных класса, легко тестируем и оформлен composer-пакетом.

Does it work

Для демонстрации, как Eventable работает и в каких случаях это может быть пригодно, ниже приведен легкой пример.

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');
}

Резюльтат

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
Допустимо пригодные ссылки

Вдохновился:

Получилось:

 Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB