Symfony2: Event system
Недавно в очередной раз столкнулся с задачей ведения рейтинга пользователя и решил ее немного абстрагировать и сделать отдельный бандл. На проекте возникла необходимость кэширвать текущее значение очков пользователя. Для управления запиями рейтинга у меня есть отдельный сервис - RatingManager. Так как система управления записями о рейтинге централизована посредством этого менеджера, то для слежения за изменением очков пользователя я решил внедрить в бандл использование событий.
Итак, в чем заключается идея системы событий? В общих чертах, все похоже на паттерн Publisher/Subscriber:
Все довольно просто, рассмотрим это на примере symfony2:
- Диспетчер событий (Symfony\Component\EventDispatcher\EventDispatcher) - выступает в роли Publisher'a, отвечает за транслирование этих событий на всю систему, можно сказать, посылает broadcast message с событием
- Событие (Symfony\Component\EventDispatcher\Event) - это и есть событие, которое мы будем транслировать, если рассматривать на примере картинки с паттерном, то это Address Changed
- Слушатель или Подписчик (Listener/Subscriber) - это сервис, который мы подписываем на конкретное событие. Если верить stackoverflow, то единственное значимое различие между этими двумя сервисами, это то что Слушатель подписывается на статичное событие посредством описания сервиса в конфиге, подписчик же может быть динамически подписан на событие в ходе выполнения приложения.
Теперь на конкретном примере задачи с рейтингом: посредством RatingManager’a мы каким либо образом меняем рейтинг пользователя, в свою очередь RatingManager транслирует событие, мол “Рейтинг изменен”, далее мы создаем Listener для этого события и выполняем нужные нам действия.
Теперь рассмотрим все по компонентам системы:
1. Event
Рассмотрим класс события RatingUpdatedEvent:
Ничего сложного, просто сохраняю нужную мне информацию, чтобы потом в Listener’e выполнить нужные манипуляции с данными, в т.ч. сохранить количество очков пользователя.
2. Event Wrapper
При транслировании события через диспетчер, мы должны указывать id события, событий может быть много, мы можем менять их id в процессе рефакторинга, поэтому я решил их обернуть в отдельный класс RatingEvents
Данный идентификатор используется в методе dispatch диспетчера событий, а также используется в описании сервиса Listener’a чтобы указать какое событие ему нужно слушать.
3. RatingManager
Все компоненты для транслирования события готовы. Для менеджера рейтинга приведу только часть кода, посылающую событие:
Здесь стоит обратить внимание на строчку $this−>dispatcher = $container−>get(‘event_dispatcher’), мне пришлось потратить некоторое время чтобы выяснить, что в symfony все таки есть общесистемный диспетчер событий, в документации я не нашел ни слова про него.
Далее по коду внутри метода registerEvent я создаю новое событие, которое унаследовал от базового класса Event, добавив к нему некую свою, нужную информацию, а затем посредством диспетчера я транслирую это событие системе с идентификатором RATING_UPDATED.
4. Listener
Последнее, что я сделал, это написал для всего этого Listener:
И описание сервиса Listener’a:
Посредством тегов мы сообщаем symfony, что наш сервис - это Listener (name: kernel.event_listener), что он слушает событие rating.updated (мы использовали этот идентификатор выше) и что при получении этого события сервис должен запускать метод onRatingUpdate, который в качестве аргумента получит событие. А далее, используя данные внутри события я обновляю данные пользователя.