Symfony Components, Event Dispatcher (теория, часть 1)

Привет. Данный перевод задумывается как первая (всего будет две) часть документации по компоненту Event Dispatcher. Этот компонент входит в семейство Symfony компонент, но в то же время он независим и его можно использовать не подключая фреймворк, что делает его еще более ценным. Перевод еще можно воспринимать как обзор легковесной реализации паттерна Наблюдатель (Observer) в php, который призван усилить взаимодействие между классами. 

Хочу сказать что семейство компонент сейчас активно перерабатывается для совместимости с версией PHP >= 5.3, и планируется использовать с новой версией фреймворка Symfony 2. Код новой версии компоненты можно посмотреть здесь. Названия и суть методов в новой редакции почти не поменялись, так что материал будет полезен и изучающим код компонент под PHP 5.3. Итак начнем.

Event Dispatcher Component — что это?

Symfony Event Dispatcher — это PHP библиотека, представляющая собой легковесную реализацию шаблона проектирования Наблюдатель (Observer). Это хороший путь сделать ваш код гибче. Это также хороший путь сделать код пригодным для расширения сторонними разработчиками (разработка плагинов). Сторонний код прислушивается к специфическим событиям путем создания обратных вызовов (callbacks), а диспетчер делает вызовы когда ваш код извещает эти события.

Очень быстрый

Главное преимущество компонента Event Dispatcher в Symfony это быть настолько быстрым насколько это возможно. Нет надобности объявлять интерфейсы или расширять сложные классы, события представляют собой простые строки, а код оповещения очень легковесный. Добавляйте любое количество обработчиков и вызовов без дополнительных проблем.

Вступление

Объектно ориентированный подход проделал длинный путь чтобы код ваших проектов был расширяемым. Созданием классов с четко определенным функционалом вы делаете код более гибким.

Если пользователь хочет изменить поведение класса, он может расширить его используя субкласс для переопределения поведения. Но если пользователь хочет распространить эти изменения на других пользователей, которые сделали свои субклассы для изменения поведения, код наследования становится неоднозначным.

Как пример из жизни, может быть вы захотите предусмотреть систему плагинов для своего класса. У плагина должна быть возможность добавлять методы, или делать что-то перед началом или по окончании работы метода, без взаимодействия с другими плагинами. Эту проблему нелегко решить путем единичного наследования, а множественное наследование (если бы оно было возможно в PHP) имеет свои недостатки.

Главная цель компонента Symfony Event Dispatcher — это позволить объектам общаться вместе не зная друг друга. Это становится возможным благодаря центральному объекту, диспетчеру (dispatcher).

Объекты (обработчики, listeners) могут связываться с диспетчером для прослушивания специфических событий, и некоторые другие могут посылать события диспетчеру. Как только событие послано, диспетчер запустит соответствующие обработчики.

События

В отличие от многих других реализаций Наблюдателя, вы не должны создавать класс для создания нового события. Все события, конечно же, остаются объектами, но все события есть экземплярами встроенного класса sfEvent.

Примечание: конечно же вы можете расширить класс sfEvent для расширения события далее, или включить некоторые ограничения, но в большинстве случаев это добавит новый неоправданный уровень сложности.

Событие однозначно идентифицируется строкой. По соглашению, лучше всего использовать буквы в нижнем регистре, цифры и нижние подчеркивания (_) для имен событий. Кроме того, для лучшей организации ваших событий, хорошим соглашением будет префиксировать имена событий пространством имен, за которым следует точка (.).

Вот примеры хорошо названных событий:

change_culture
response.filter_content

Как вы наверное заметили, имена событий содержат слово для индикации того что должно произойти при наступлении события.

Диспетчер

Диспетчер это объект, ответственный за контроль регистра обработчиков и их вызов, когда происходит одно из событий.
По умолчанию, класс диспетчера это sfEventDispatcher:

$dispatcher = new sfEventDispatcher();

 

Объекты событий

Объект события, класса sfEvent, сохраняет информацию об объявляемом событии. Его конструктор принимает три аргумента:

  • Контекст (субъект) события (в большинстве случаев это объект объявляющий событие, но может быть и null);
  • Имя события;
  • Массив параметров для передачи их обработчикам (по умолчанию пустой массив).

Чаще всего событие вызывается в контексте объекта, первый аргумент почти всегда $this:

$event = new sfEvent($this, 'user.change_culture', array('culture' => $culture));

У объекта события есть несколько методов для получения информации о событии:

  • getName(): возвращает идентификатор события;
  • getSubject(): возвращает объект субьекта (контекст), пристыкованного к событию;
  • getParameters(): возвращает массив параметров события.

Объект события может быть также использован как массив для получения параметров:

echo $event['culture'];

 

Добавление обработчиков

Очевидно, вам нужно присоединить некоторые обработчики к диспетчеру перед тем как он может быть полезным. Обращение к методу диспетчера connect() ассоциирует PHP callable с событием.

Метод connect() принимает два аргумента:

  • Имя события;
  • PHP callable для вызова, когда событие произойдет.

Примечание: PHP callable это переменная PHP, которая может быть использована функцией call_user_func() и возвращает true когда передается в функцию is_callable(). Строка представляет функцию, а массив может представлять метод объекта или метод класса.

$dispatcher->connect('user.change_culture', $callable);

Как только обработчик зарегистрирован с помощью диспетчера событий, он ждет вызова соответствующего события. Диспетчер событий хранит запись всех обработчиков событий, и знает какой из них вызвать когда произойдет событие.

Примечание: обработчики вызываются диспетчером событий в том порядке, котором вы их присоединяли.

Для предыдущего примера, $callable будет вызвано диспетчером тогда, когда user.change_culture событие будет объявлено объектом.
Когда вызываются обработчики, диспетчер передает им объект sfEvent как параметр. То есть, обработчик получает объект события как свой первый аргумент.

Объявление событий

Событие может быть объявлено одним из трех методов:

  • notify();
  • notifyUntil();
  • filter();
notify()

Метод notify() запускает в оборот все обработчики.

$dispatcher->notify($event);

Используя метод notify(), вы можете быть уверены что все зарегистрированные обработчики объявленного события были выполнены но ни один из них не может возвратить значение субъекту.

notifyUntil()

В некоторых случаях, вам нужно позволить обработчику остановить событие и препятствовать тому чтобы другие обработчики узнали о происшедшем событии. В этом случае, вам нужно использовать notifyUntil() вместо notify(). Тогда диспетчер вызовет все обработчики пока один из них не вернет true, и после этого остановит реакцию на событие:

$dispatcher->notifyUntil($event);

Обработчик, который остановит цепочку может также вызвать метод setReturnValue() для возврата субъекту некоторого значения.

Тот кто вызвал событие может проверить что обработчик обработал событие путем вызова метода isProcessed():

if ($event->isProcessed())
{
$ret = $event->getReturnValue();

// ...
}

 

filter()

Метод filter() требует все обработчики фильтровать заданное значение, передаваемое создателем события во втором аргументе, и получаемое обработчиком как второй аргумент:

$dispatcher->filter($event, $response->getContent());

Все обработчики получают значение и они должны возвратить отфильтрованное значение, которое они изменили или нет. Все обработчики гарантированно будут вызваны.

Тот кто объявил событие может получить отфильтрованное значение вызвав метод getReturnValue():

$ret = $event->getReturnValue();

Далее во второй части я планирую перевод практических примеров использования Event Dispatcher. А затем, возможно, будет еще топик по использованию компонента в реальном проекте.