image
Привет. Данный перевод задумывается как первая (всего будет две) часть документации по компоненту 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. А затем, возможно, будет еще топик по использованию компонента в реальном проекте.

Комментарии (17)


  1. xtech
    13.04.2010 08:59

    Мне казалось что Symfony Components и Symfony 2 чуть-чуть разные вещи, хотя первые присутствуют во втором, слегка подправленные для работы с PHP 5.3.


    1. skorney Автор
      13.04.2010 08:59

      Насколько я понял, философия Symfony 2 такова: легковесное ядро с подключаемыми компонентами. То есть компоненты могут использоваться независимо от фреймворка, но разрабатываются они «с прицелом» тесной с ним интеграции. Компоненты также входят во фреймворк Symfony 2.


      1. xtech
        13.04.2010 08:59

        Ну сами компоненты были представлены раньше чем Symfony 2 и на страницах откуда вы делали перевод ни слова не сказано про Symfony 2. При всем этом компоненты входящие в Symfony 2 будут работать только на PHP версии >=5.3, в отличии от компонент представленных в вашем переводе.


        1. skorney Автор
          13.04.2010 08:59

          В этом переводе речь идет об одной компоненте — Event Dispatcher. Очевидно (видно использование namespace) что он работает только с PHP версии >=5.3. Эти компоненты есть частью фреймворка Symfony 2 — это понятно при работе с фреймворком. Ну например сразу при запуске приложения вызывается bootstrap.php в котором подключается Event Dispatcher.

          Тем более здесь в абзаце «About the Symfony Components» написано, что суть компонент: они разрабатывались как часть фреймворка, но потом было решено их как-бы отделить и сделать независимыми.

          Переписать компоненты на PHP 5.3 было решением, по моему мнению, чтобы их интегрировать с Symfony 2, который на 5.3.


          1. xtech
            13.04.2010 08:59

            Не поймите меня неправильно, просто я читая ваш перевод слегка запутался в понятиях, так как перевод относится к Symfony Components и ссылку даете именно на них, а пишите про какие-то Symfony 2 Components про которые нигде ни пол слова не сказано. По моему упоминание про Symfony 2 здесь уместно в виде отдельной сноски со словами о том что эта компонента присутствует в новом фрэймворке


          1. chEbba
            13.04.2010 08:59

            Попробуем все таки прояснить.

            Есть у симфони набор стенлэлоун компонентов components.symfony-project.org/
            Есть их стабильная версия под пхп 5.2.
            Есть документация стабильной версии, которую вы перевели components.symfony-project.org/event-dispatcher/documentation

            Кроме этого в рамках 2ой версии симфонии идет переработка и этих компонентов.
            Соответственно это нестабильная версия.
            Эта версия под 5.3 с неймспейсами и т.п.


            1. chEbba
              13.04.2010 08:59

              стенДэлоун*


            1. skorney Автор
              13.04.2010 08:59

              Да вы правы. Получается перевод стабильной версии, та, которая на 5.2.
              Я перепутал классы ветки фреймворка 1.x и классы ветки 2.x.

              В переводе я подправлю что это все касается ветки 1.x, но идет переработка компонент для новой ветки. Вроде бы я вас правильно понял.


              1. xtech
                13.04.2010 08:59

                По моему лучше было бы: «Symfony Components, Event Dispatcher», так как компоненты развиваются как стэндэлоун классы.


  1. weirdan
    13.04.2010 08:59

    and multiple inheritance (were it possible with PHP) has its own drawbacks.

    а множественное наследование (которое возможно в PHP) имеет свои недостатки.

    стоило перевести как
    а множественное наследование (если бы оно было возможно в PHP) имеет свои недостатки.


    1. skorney Автор
      13.04.2010 08:59

      Исправил, спасибо.


      1. AmdY
        13.04.2010 08:59

        для этого есть наследование.

        p.s. я сам не пользовался spl-ским обсервером, думал мот нашли какие-то грабли


  1. AmdY
    13.04.2010 08:59

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


  1. anonymous
    13.04.2010 08:59


  1. vitalio
    13.04.2010 08:59

    непонятна фраза:
    и возвращаемое обработчиком callable как второй аргумент
    Не хватает примера демонстрирующего как обработчик получает значение, фильтрует его и передает дальше.
    И если вызываются 3 обработчика, то 2-й на вход получит значение отфильтрованное 1-м, а 3-й соответственно значение отфильтрованное 2-м?


    1. weirdan
      13.04.2010 08:59

      В оригинале retrieved by the listener callable as the second argumentполучаемое обработчиком как второй аргумент


      1. skorney Автор
        13.04.2010 08:59

        Спасибо, подправил. Я заменил listener callable просто как обработчик. Меня сбило с толку это понятие — PHP callable