События и обработчики событий давно не новость в объектно-ориентированном программировании: общеизвестен паттерн проектирования Observer [1], который используется в изрядном количестве библиотек и программных продуктов. Меня интересует использование этого паттерна в PHP5.
Самая простая, классическая реализация этого паттерна наличествует в SPL [2], где этот паттерн определён интерфейсами SplSubject и SplObserver. Простенько, теперь — со вкусом PHP.
Во фреймворке Prado [3] система событий очень мощно развита, недаром он называется «событийно-ориентированным». События порождаются в изрядном количестве, к любому из событий произвольного компонента можно привязать произвольное количество произвольных обработчиков. Естественно, большая часть событий относится к циклу создания страницы как таковой и к действиям пользователя: Prado ориентирован на создание веб-страниц в стиле ASP.NET [4] с его концепцией code behind. Хотел было написать, что это единственный из найденных мной фреймворков для PHP5, где события играют важную роль в организации жизненного цикла приложения, но затем вспомнил, что есть ещё и Yii [5], там тоже есть события, реализация очень похожа на реализацию Prado, что неудивительно: автор тот же. Порывшись для уверенности в Google, нашёл фреймворк PHP_Application [6], но глубоко изучать не стал.
Достаточно удобно сделано навешивание обработчиков на события уровня бизнес-логики в достаточно известном движке блогов WordPress [7].
Также имеется несколько достаточно простых реализаций диспетчера событий — в частности, Event_Dispatcher [8], пакет PEAR.
Мне давно хочется обсудить использование событий в приложениях на PHP. Нужно ли? Осмысленно ли? И, если ответ на первые два вопроса утвердительный, то где, в каких случаях? Лично мне кажется, что в событийно-ориентированных приложениях удобно расширять функционал, но они сложны как в проектировании, так и в тестировании. События могут использоваться как дополнительная «фишка» фреймворка, но быть краеугольным камнем, на котором построен весь функционал приложения, они не должны. Причин тому несколько:
- глядя на диаграммы и на код, мы не видим, что происходит параллельно вызовам тех или иных методов, то есть изменение состояния системы для нас неочевидно;
- в этой связи нам необходимо очень внимательно добавлять и удалять обработчики событий: повесить два обработчика одного и того же события или не повесить ни одного очень легко, а искать ошибку такого рода очень сложно;
- сложность проектирования самих классов событий очень велика, так как заставляет в событии размещать всю возможную информацию, которая может понадобиться обработчику; либо нам нужно «вытаскивать» необходимые данные субъекта в обработчике.
Ваши мнения?
Угу, ещё есть такой момент – навешивание событий дело довольно хлопотное, если события привязаны к объектам, соответствующие объекты должны быть созданы (как инициаторы, так и получатели), можно конечно делать всяких лёгких ленивых вызывателей, и возможны варианты, когда события навешиваются в диспетчер, когда ни тот ни тот объект не создан – это вариант такого “глобального” обработчика для всех событий (тогда в частности надо порой разбирать отправителя на тему, а тот ли это отправитель, что нужно)… опять же, можно навешивать 100 обработчиков, из которых за обработку выстрелит 5.
Я тут сейчас обмозговываю разные аспекты фреймворков, что как… в общем пришёл примерно к тому же, к чему и ты – сам основной фреймворк – без событий (они не закладываются в интерфейсы основных объектов), но если надо – пожалуйста, делайте свою реализацию роутера, контроллера, да чего угодно, которая умеет работать с событиями. Что же касается конкретного расширения функционала там, где это нужно – коллбэки, где просто или стратегии, где сложнее. Это как-то более явно получается.
Насчет событий вообще – не согласен с тобой. Они не усложняют приложение. Да, на первый взгляд не видно взаимодействие объектов, но это же способствует уменьшению связей, делает более внятные интерфейсы и позволяет сосредоточиться на реакции на событие. Другими словами, мы убираем из связи между компонентами информацию друг о друге.
Но применение событий в php – другое дело. Тут я против, кроме некоторых частных случаев. Проблему вижу в производительности. В “нормальных” приложениях большая часть связей между объектами и событиями устанавливаются один раз при старте. В дальнейшем изменения будут достаточно мелкие и вся эта сеть связей будет просто работать. В php же, мы будем настраивать связи при каждой генерации страницы. Плюс некоторая часть из них может вообще не понадобиться в данном прогоне (ну это больше от фреймворка зависит). В общем, думаю, что цель не оправдывает средства.
Плюс лепить события куда угодно в любом случае не следует. А тем более строить на них фреймворк. С моей точки зрения событие, это все-таки более специфичная вещь, чем вызов метода и должна применяться соответственно.
Почему я и говорю, что применять нужно осмотрительно и там, где это действительно нужно.
В моем представлении событие – это… ну событие и есть.
Пользователь нажал кнопку, получен пакет данных из сети, завершение приложения. Т.е. вещи, чаще всего внешние по отношению к системе, о которых нужно уведомить множество компонентов, причем заранее их количество неизвестно.
Не работает он с такими вещами, которые могут расцениваться как событие. Вернее есть, но мало.
Например, то же завершение работы – можно было бы пробежаться по всем объектам и вызвать у них метод прекращения работы. Но во-первых, не всем это надо, во вторых, механизм получается сложнее, чем событие.
Кстати, еще один аргумент против использования событий в php – у него просто нет событий.
Ну работать будет, никто не спорит.
События. Хотя насчет товара можно поспорить. Только для всего этого будет выполняться новый прогон всех скриптов, а не использование уже существующей схемы событий. То, что я говорил раньше.
Но все равно, будет отправлен запрос на сервер. Там интерпретатор обработает необходимые (или все
) скрипты. Для этого все равно прийдется заново подключать обработчики событий, все настраивать и уже только потом выполнять собственно действие.
В случае приложений реального времени, если пользователь нажал “авторизоваться” – то будут использоваться уже настроенные обработчики, все, что нужно сделать – это прореагировать на событие, а не предварительно все настроить.
Закэшировать результаты настройки сети событий на часик например в файлик (или в память, хотя не думаю что размер его будет больше 4кб, а это значить что он и так будет в памяти, точнее в буфере файловой системы). И юзать его пока он валидный.
В общем, согласен. Кстати, ленивая инициализация обработчиков событий — это как раз та конфета, которая меня сильно манила. Но если говорить про PHP со всей присущей ему спецификой, то может выйти так, что затраты на создание всех этих неочевидных ленивых связей будут гораздо больше, чем на прямой вызов. Нетушки, проще в явном виде всё это делать.
Кстати, я думаю, что совсем-совсем от событий уровня приложения не отказываюсь; а вот с привязкой ко всем объектам завязываю.
Именно так. Мы «убираем информацию друг о друге» и получить эту информацию, когда мы начинаем смотреть, что у нас, собственно, происходит, нам неоткуда.
Событие естественно более специфичная вещь, но «куда угодно» — это очень общо. Куда их лепить правильно, а куда нет?
Что касается фреймворка — ну, как видишь, лепят.
Кстати! Где я сказал, что события усложняют приложение? Я говорил, что они усложняют понимание приложения. Это разное, имхо.
Эм? Поступил запрос HTTP — это не событие? Пользователь авторизовался в системе — это не событие? Пользователь купил товар — это не событие?
А что тогда события?
Для авторизации? Каких «всех»? У тебя формочка логина при помощи AJAX заменится на приветствие, скажем, и всё.
А, ну понял мысль. Более-менее. Только не очень врубился в термин «приложения реального времени».
Да, именно так.
Кстати, в том же Вордпрессе связки с обработчиками хранятся тихо-мирно в базе данных. По-моему, если задаваться целью создания не просто фреймворка, а CMF, то это не просто разумно, а очень разумно.
Да БД это наверно самое лучшее решение для этого. Хотя… даже не удобно говорить
… у меня есть такие маленькие проектики, где БД просто не уместна.
А вот это уже зависит от размеров проекта. Имхо, от ма-а-а-аленькой служебной БД в том же SQLite хуже не станет.