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

Автор оригинала: Fabien Potencier, Sensio Labs
  • Перевод
image
Привет. Это вторая часть перевода документации по Symfony компоненту Event Dispatcher, первая часть здесь. Вторая часть перевода представляет собой сборник общих рецептов по использованию компонента Event Dispatcher. Для тестирования приведенных примеров не нужно подключать фреймворк Symfony — как отмечалось в первой части, компоненты независимы от фреймворка. Еще раз хочу отметить, что код Symfony компонентов сейчас перерабатывается для использования с Symfony 2 (PHP >= 5.3.2). Данный перевод относится к стабильной версии компонента Event Dispatcher. Но, насколько я понял из сравнения стабильной версии компонента с текущей под Symfony 2, — функционально они мало чем отличаются, то есть документация будет полезна и использующим новую версию компонента (PHP >= 5.3.2). Итак начнем.

Пройдемся по возможностям объекта Event Dispatcher


Если вы просматривали код класса sfEventDispatcher, вы должны были отметить, что класс не работает по принципу шаблона проектирования Singleton (в нем нет статического метода getInstance()). Это сделано специально, так как вы можете захотеть работать с несколькими конкурирующими диспетчерами событий в одном PHP запросе. Но это также подразумевает, что вам нужен способ передачи диспетчера объектам, которые должны взаимодействовать с событиями или их порождать.

Лучшей практикой будет включение объекта диспетчера событий в ваши объекты по шаблону «внедрение зависимости» (dependency injection).

От себя замечу: Мартин Фаулер (Fowler) выделяет 3 пути по которым объект может получать ссылку на внешний модуль, в соответствии с используемым шаблоном внедрения зависимости:
  1. Внедрение интерфейса или (interface injection), в котором внешний модуль обеспечивает интерфейс, которого его пользователи должны придерживаться для получения зависимостей во время выполнения;
  2. Внедрение установщика (setter injection), в котором зависимый модуль предоставляет метод установщика (setter), который фреймворк использует для внедрения зависимости;
  3. Внедрение конструктора (constructor injection), в котором зависимости обеспечиваются через конструктор внешнего класса.
Вы можете использовать внедрение конструктора (constructor injection):
class Foo {
 protected $dispatcher = null;

 public function __construct(sfEventDispatcher $dispatcher) {
  $this->dispatcher = $dispatcher;
 }
}

Или внедрение установщика (setter injection):
class Foo {
 protected $dispatcher = null;

 public function setEventDispatcher(sfEventDispatcher $dispatcher) {
  $this->dispatcher = $dispatcher;
 }
}
Решение, какой из этих способов использовать — дело вкуса. Я предпочитаю использовать внедрение конструктора, так как объекты полностью инициализируются на этапе создания. Но когда у вас есть большой перечень зависимостей, использование внедрения установщика может быть лучшим путем, особенно для опциональных зависимостей.

Примечание: если вы используете внедрение зависимости как в наших предыдущих двух примерах, вы легко можете использовать контейнер Symfony Dependency Injection для более элегантного управления этими объектами.

Делаем что-то до или сразу после вызова метода


Если вы хотите сделать что-либо до, или сразу после вызова метода, вы можете объявить событие, соответственно, в начале или в конце метода:
class Foo
{
// от себя:
// можно объявить некоторый статический метод (вызываем перед самим методом):
// static public function do_before($arr){
//  echo "before!!!";
// }

public function send($foo, $bar)
{
 // делаем что-то до вызова метода
 $event = new sfEvent($this, 'foo.do_before_send', array('foo' => $foo, 'bar' => $bar));
 // от себя: привязываем событие к вызову статического метода:
 // $this->dispatcher->connect('foo.do_before_send', 'Foo::do_before');
 $this->dispatcher->notify($event);

 // реально метод выполняется здесь
 // $ret = ...;

 // делаем что-то после вызова метода
 $event = new sfEvent($this, 'foo.do_after_send', array('ret' => $ret));
 $this->dispatcher->notify($event);

 return $ret;
}
}

Добавление методов к классу


Чтобы позволить многим классам добавлять методы друг другу, вы можете объявить «магический» метод __call() в классе, который вы хотите расширить, таким образом:
class Foo
{
 // ...

 public function __call($method, $arguments)
 {
  // создаем событие под названием 'foo.method_is_not_found'
  // и передаем название метода и передаваемые аргументы в этот метод
  $event = new sfEvent($this, 'foo.method_is_not_found', array('method' => $method, 'arguments' => $arguments));

  // вызываем все обработчики пока один не сможет выполнить $method
  $this->dispatcher->notifyUntil($event);

  // ни один обработчик не смог обработать событие? Метод не существует
  if (!$event->isProcessed())  {
   throw new sfException(sprintf('Call to undefined method %s::%s.', get_class($this), $method));
  }

  // возвращаем значение, которое вернул обработчик
  return $event->getReturnValue();
 }
}

Теперь создадим класс который будет хранить обработчик:
class Bar
{
public function addBarMethodToFoo(sfEvent $event)
{
 // мы только хотим ответить на вызовы метода 'bar'
 if ('bar' != $event['method'])
 {
  // позволяем другому обработчику позаботится об этом неизвестном методе
  return false;
 }

 // объект контекста создания события (экземпляр класса foo)
 $foo = $event->getSubject();

 // аргументы метода bar
 // от себя замечу, в документации может быть ошибка,
 // у меня заработало так:
 // $arguments = $event['arguments'];
 $arguments = $event['parameters'];

 // делаем что-либо
 // ...

 // устанавливаем возвращаемое значение
 $event->setReturnValue($someValue);

 // сообщаем миру что мы обработали событие
 return true;
}
}

В конце концов, добавляем новый метод bar к классу Foo:
$dispatcher->connect('foo.method_is_not_found', array($bar, 'addBarMethodToFoo'));

Модифицируем аргументы


Если вы хотите позволить внешним классам модифицировать аргументы, передаваемые методу сразу перед его выполнением, добавьте событие фильтра в начало метода:
class Foo
{
// ...

public function render($template, $arguments = array())
{
 // фильтруем аргументы
 $event = new sfEvent($this, 'foo.filter_arguments');
 // от себя: в тестах я писал так:
 // $this->dispatcher->connect('foo.filter_arguments', array(new Bar(), 'filterFooArguments'));
 $this->dispatcher->filter($event, $arguments);

 // получаем отфильтрованные аргументы
 $arguments = $event->getReturnValue();

 // ... здесь начинается метод
 // от себя: ну например
 // return $arguments;
}
}

В роли фильтра может служить такой класс:
class Bar {
public function filterFooArguments(sfEvent $event, $arguments) {
 // от себя: для того чтоб увидеть изменения
 // $arguments = array('33', '44');
 $arguments['processed'] = true;
 return $arguments;
}
}

Вот пока что все. Далее хотелось бы написать про то, чем отличаются новые версии компонент на PHP 5.3.2 от их стабильных аналогов на PHP 5.2. Или же начну писать про компонент Dependency Injection. Еще, я думаю, было бы интересно написать про то как Sensio Labs тестирует код (например, тех же Components при помощи PHPUnit). До новых встреч.
Поделиться публикацией

Комментарии 12

    0
    пожалейте наши глаза!!!
      +1
      Согласен, зрение это очень важно!
      Сделал камменты в коде серыми
      0
      Спасибо за переводы, очень интересно, буду ждать про Dependency Injection
      0
      Спасибо!

      Кстати, контрвопрос ко всем: кто-нибудь досконально разбирался в построении новых bundle'ов-плагинов (в смысле, создания) и взаимодействия оных между собой (например, прикладной bundle, использующий propel или doctrine)
        0
        Я вообще не видел пока что нормального полноценного проекта на Symfony 2 с использованием Doctrine 2.
        Все ссылаются на известные две демки (sandbox и ajax demo) — но работы с БД я там не увидел.
        Вот толковый пост по интеграции по интеграции с Doctrine 2, но еще детально не разбирался.
          0
          За ajax_demo спасибо, попробую разобраться на досуге. Я просто думал попробовать поделать под symfony 2 bundle, чтобы лучше разобраться в устройстве приложений. Компоненты — это хорошо, но я больше по прикладному аспекту =)
            0
            Сорри, я вот эту ссылку хотел в предыдущем камменте написать.
            А вообще планирую разобраться все-таки как подключить Doctrine 2 к Simfony 2 и написать об этом пост на хабре в ближайшее время — такого вроде еще нет.
        +3
        Не сочтите за самопиар, токмо из научного рвения.

        Я считаю, что разработчики Симфонии, действительно олицетворяют движение к революционно новому и правильному развитию. И их шаг в сторону DI, это революция для php-разработки.

        Ведь во-первых можно использовать их DI-контейнер независимо (http://components.symfony-project.org/dependency-injection/).

        И во-вторых, можно использовать совсем минимальный DI-контейнер Phemto, который написал Маркус Бейкер, автор Simpletest. Там кода менее 1000 строк. Осмелюсь рекомендовать мой перевод статьи, как это чудо использовать:
        h-type.com/filez/lj/articles/di/

        И тоже самое, незначительно устаревшее на хабре:
        habrahabr.ru/blogs/php/64061/
        habrahabr.ru/blogs/php/64078/

        Думаю после принятия этого паттерна сообществом, так же как и принятие MVC, ActiveRecord и пр. должен быть бум внедрения сторонних библиотек. Ведь именно DI позволяет построить мостик между вашим фреймворком и кусочком чужого фреймворка.
          +1
          По поводу полезности компонент от Sensio Labs я с вами согласен.
          Но мысль такая: разве указанные компоненты (ED и DI) по сути не являются просто воплощением идей Гамма, Фаулера, Хелма и команды в виде контейнера на языке PHP? Вроде бы ничего революционного. Просто другим лень написать толковые классы.

          Еще вопрос: я вот читаю всем известную книгу Гамма, Хелма, Джонсона, Влассидеса — там в перечне 23 паттерна. Где взять расширенный перечень, куда бы входил паттерн Dependency Injection?
            0
            1. Там в GoF и MVC нету. Она рассматривает более простые паттерны и более старые. Это у фаулера надо смотреть, в PoEEA (http://martinfowler.com/eaaCatalog/plugin.html)

            martinfowler.com/articles/injection.html#InversionOfControl

            2. Насчет лень толкать. Так пойди толкни, когда оно сложно изложено и ум разработчика сопротивляется лишним накладкам. Симфони молодцы именно тем, что у них относительно просто и на PHP, а не на джаве. А в Phemto и того проще.
              +1
              Написать толковые классы и есть воплощение мастерства программиста это не так просто, жеж. Харизма нужна.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое