Приложения, работающие на основе фреймворка Webasyst, могут поддерживать события. Событие — это место в коде приложения, в котором к приложению можно подключить дополнительный программный код, не внося изменения в само приложение. Дополнительный программный код может выполняться в другом программном продукте: в плагине или стороннем приложении. Должно ли ваше приложение поддерживать события, вы решаете самостоятельно.
Как это работает
Простой пример:
- Пользователь открывает страницу сайта.
- Для формирования HTML-кода страницы выполняется PHP-код приложения.
- В коде приложения есть специальное место — событие. Это место добавил разработчик приложения.
- Событие обрабатывается плагином — так решил разработчик плагина. Как только выполнение кода приложения дойдёт до события, сработает код плагина, который подключён к этому событию.
В обработке событий участвуют 2 вида программных продуктов: те, в исходном коде которых есть собственные события, и те, которые умеют обрабатывать чужие события.
Объявление событий в коде приложения
Для объявления события в PHP-коде приложения нужно добавить вызов системного метода wa('app_id')->event('event_id')
.
Пример
$result = wa('myapp')->event('backend', $params);
В этом примере:
'myapp'
— идентификатор приложения, в коде которого объявляется событие.'backend'
— идентификатор события. Его можно придумать любым — желательно так, чтобы разработчикам других приложений и плагинов было интуитивно понятно, где срабатывает это событие и как его использовать.$params
— необязательная переменная для передачи параметров, которые могут быть полезны разработчику стороннего приложения или плагина при обработке события.$result
— результат, который приложение может получать от плагина или другого приложения. Если никакой результат не ожидается, то можно его не использовать.
Подписка на события приложений в плагинах
Для того чтобы для какого-то события выполнялся программный код плагина, разработчик плагина должен подписать свой плагин на этой событие. Подписать — значит, заявить, что при наступлении данного события сработает код в плагине. Для этого нужно добавить элемент 'handlers'
в конфигурационный файл plugin.php
.
Подписываться на события приложения можно несколькими способами:
- один метод для обработки одного события своего приложения
- несколько методов для обработки одного события своего приложения
- один метод для обработки нескольких событий своего приложения
- обработка событий других приложений
Способ 1. Один метод для одного события
Пример
'handlers' => array( 'backend_menu' => 'backendMenu', ),
Такая запись означает, что в основном классе плагина есть публичный нестатический метод с именем backendMenu
, который должен выполняться при наступлении события backend_menu
— в том же приложении, для которого написан этот плагин. Как только сработает это событие, выполнится код в указанном методе плагина.
Способ 2. Несколько методов для одного события
Пример
'handlers' => array( 'backend_menu' => array('backendMenu', 'commonHandler', '...'), ),
Такая запись означает, что в классе плагина есть публичные методы с именами, указанными в виде массива, — они должны выполняться при наступлении события backend_menu
— в приложении, для которого написан плагин. Как только сработает это событие, выполнится код в указанных методах плагина.
При наступлении события код в этих методах выполняется по очереди — в той же последовательности, в какой они указаны в конфигурационном файле, и до получения первого результата, отличного от null
. Как только любой из этих методов вернёт результат, отличный от null
, выполнение остальных методов прекращается.
Способ 3. Один метод для нескольких событий: несколько записей
Пример
'handlers' => array( 'backend_layout' => 'layoutEvent', 'frontend_layout' => 'layoutEvent', ),
Такая запись означает, что для обработки событий backend_layout
и frontend_layout
в плагине используется один и тот же метод — в этом примере он называется layoutEvent
.
Способ 4. Один метод для нескольких событий: маска
Пример
'handlers' => array( 'order_action.*' => 'orderAction', ),
Если у приложения есть несколько событий с похожими именами, в именах которых есть точка, то плагин может подписаться сразу на все такие события, заменив отличающуюся часть имени события звёздочкой *. В этом примере у приложения могут быть события с именами order_action.create
, order_action.pay
, order_action.edit
— с помощью звёздочки в качестве маски плагин может подписаться сразу на все такие события и обрабатывать их в одном методе.
Использовать звёздочку можно только для событий, в названиях которых есть точка. Чтобы использовать маску для подписки на события без точки в именах, запишите регулярное выражение.
Пример 1
'handlers' => array( '~(backend_frontend)_layout~' => 'layoutEvent', ),
В качестве разделителей регулярных выражений можно использовать символы ~
или /
.
Пример 2
'handlers' => array( '~[a-z]+_layout~' => 'layoutEvent', ),
Этот пример похож на предыдущий, но в этом случае регулярное выражение более «жадное» — ему могут соответствовать больше событий: не только backend_layout
и frontend_layout
, но и, к примеру, dialog_layout
или email_layout
— если такие события есть в коде приложения.
Нужно с осторожностью подписывать плагины на события с использованием регулярных выражений, чтобы случайно не подписаться на лишние события — это добавит лишнюю нагрузку на сервер или сделает поведение приложения или плагина непредсказуемым.
Способ 5. Обработка событий других приложений
В предыдущих примерах было показано, как плагин может обрабатывать события того же приложения, для которого написан плагин. Но плагин может обрабатывать и события других приложений. Для этого нужно использовать расширенный формат подписки. Этот формат требует добавить в массив 'handlers'
подмассив с ключом '*'
. В подмассиве нужно указать следующие значения:
- event_app_id — идентификатор приложения, события которого нужно обрабатывать;
- event — регулярное выражение для обозначения событий, на которые подписывается плагин;
- class — имя PHP-класса, в котором находится метод-обработчик события;
- method — имя метода-обработчика.
Пример 1
'handlers' => array( '*' => array( array( 'event_app_id' => 'otherapp', 'event' => 'event_name', 'class' => 'someappMyPlugin', 'method' => 'eventHandler', ) ), ),
В этом примере для обработки события в поле 'event_app_id'
указано другое приложение. Если нужно, то и в этом случае имена нескольких событий можно указать в виде массива или с помощью регулярного выражения.
Пример 2
'handlers' => array( '*' => array( array( 'event_app_id' => '*', 'event' => 'signup', 'class' => 'someappMyPlugin', 'method' => 'signup', ) ), ),
Это более универсальный пример — в нём точно не указано приложение, на события которого подписывается плагин. Вместо идентификатора приложения указана звёздочка *
— это значит, что плагин будет обрабатывать события с именем, указанным в поле 'event'
, возникающие в коде любых приложений. Примером такого события может быть signup
— оно объявлено в коде системных классов фреймворка, но, т. к. эти классы используются разными приложениями, значит, для обработки события плагин должен учитывать его в работе всех установленных приложений.
Пример 3
'handlers' => array( '*' => array( array( 'event_app_id' => '*', 'event' => '~.+~', 'class' => 'someappMyPlugin', 'method' => 'eventHandler', ) ), ),
Этот пример — самый универсальный из всех: плагин подписывается на все события всех приложений. Скорее всего, такой формат вам никогда не потребуется, потому что его использование потребует много серверных ресурсов. Использовать его стоит только в исключительных случаях.
Подписка на события приложений в других приложениях
Подписываться на события в приложениях можно двумя способами:
- создание PHP-класса, доступного по пути стандартного формата;
- перечисление имён классов и их методов в конфигурационном файле.
Способ 1. Создание класса по пути стандартного формата
В этом случае не нужно добавлять записи в конфигурационный файл. Достаточно в стороннем приложении создать PHP-класс обработчика. Формат пути к файлу класса-обработчика — wa-apps/app_id/lib/handlers/event_app_id.event_name.handler.php
. Формат имени класса, который должен быть объявлен в этом классе: app_idEvent_app_idEvent_nameHandler
. Класс должен наследовать системный класс waEventHandler
. Основной метод класса-обработчика должен называться execute()
.
Пример
ID приложения, в котором объявлено событие: shop.
Событие, объявленное в приложении shop
: backend_order.
ID приложения, которое обрабатывает событие backend_order
в приложении shop
: crm.
Путь к файлу с классом обработчиком события: wa-apps/crm/lib/handlers/shop.backend_order.handler.php
.
Имя класса-обработчика: crmShopBackend_orderHandler.
Код класса-обработчика:
class crmShopBackend_orderHandler extends waEventHandler { public function execute(&$params, $event_name = null) { //... } }
Способ 2. Перечисление классов и методов в конфигурационном файле
Создайте файл wa-apps/app_id/lib/handlers/wildcard.php
. Он должен содержать массив с информацией о том, какие методы каких классов должны обрабатывать события в приложении-доноре.
Пример файла wildcard.php
return array( array( 'event' => 'event_name_1', 'class' => 'app_idEvent_app_idEvent_name_1Handler', 'method' => array('execute', 'extraMethod'), 'event_app_id' => 'event_app_id_1', 'file' => 'path/to/some/class1.php', ), array( 'event' => 'event_name_2', 'class' => 'app_idEvent_app_idEvent_name_2Handler', 'method' => array('execute', 'extraMethod'), 'event_app_id' => 'event_app_id_2', 'file' => 'path/to/some/class2.php', ), array( ... ), );
В этом файле:
event_app_id
— ID приложения, событие которого нужно обрабатывать;event
— имя события, которое нужно обрабатывать;class
— имя PHP-класса, метод которого должен обрабатывать событие;method
— имя метод указанного класса, в котором содержится логика обработки события;file
— путь к файлу, в котором объявлен класс с методом-обработчиком; путь нужно явно указывать только в том случае, если класс не подключается стандартным механизмом автозагрузки.
Старайтесь указывать в файле wildcard.php
методы классов, специально созданных именно для обработки событий, а не методы обычных классов приложения. Это поможет лучше контролировать доступность всех перечисленных классов и их методов по мере развития продукта.
Оформление метода класса для обработки событий
В метод-обработчик события — и в плагине, и в приложении — передаются 2 аргумента:
- Параметры события. Это значение передаётся при вызове метода
wa()->event('event_name', $params)
в виде аргумента$params
. Для разных событий в обработчики передаются разные параметры. В том числе могут не передаваться никакие параметры — в этом случае в обработчике события будет доступно значение аргументаnull
.
Параметры, если они передаются, всегда доступны по ссылке. Поэтому их можно изменять в коде метода-обработчика, чтобы они могли дальше использоваться в приложении после обработки события, — если это предполагает логика работы приложения. - Имя события. Его удобно использовать в обработчике, например, если один метод обрабатывает несколько событий, — чтобы изменять логику обработчика в зависимости от имени конкретного события. Этот аргумент необязательный — его можно не указывать в сигнатуре метода-обработчика, если это значение не планируется использовать.
Пример 1
//указан второй аргумент для получения имени события public function eventHandler(&$params, $event_name = null) { //... }
Пример 2
//указан только один аргумент — имя события в обработчике не используется public function eventHandler(&$params) { //... }
В приложениях рекомендуется наследовать класс-обработчик от системного класса waEventHandler
и использовать в нём метод-обработчик с именем execute()
. Это позволит приложению автоматически использовать дополнительную системную функциональность для обработки событий, которая может быть внедрена в будущем.
Динамическое подключение обработчиков событий
Во время разработки и отладки может быть полезно динамически подключать обработчики событий, которые не должны работать в опубликованном коде. Для этого используйте метод waEvent::addCustomHandler($handler)
, как показано в примере. В аргументе $handler
нужно указать параметры обработчика события.
$handler = array( 'object' => new someCustomClass(), 'method' => 'eventHandler', //или array('eventHandler1', 'eventHandler2', ...) 'event' => 'some_event', 'event_app_id' => 'some_app', ); waEvent::addCustomHandler($handler);
Если в поле 'method'
перечислено несколько методов-обработчиков в виде массива, то все они будут выполнены, даже если любые методы в начале списка вернут результат, отличный от null
. Этим динамическое подключение обработчика отличается от обычных обработчиков приложений и плагинов.
Отладка обработчиков событий
При выбрасывании исключений в обработчиках событий прерывание работы приложения подавляется фреймворком, а сообщения исключений записываются в лог-файл wa-log/error.log
. Эти записи полезно использовать при отладке обработчиков в процессе разработки.
Можно логировать время работы каждого обработчика каждого события в файл. Для этого нужно авторизоваться от имени пользователя, имеющего доступ к бекенду, и добавить значение cookie event_log_execution
= 1. После этого информация об обработке событий будет сохраняться в лог-файл wa-log/webasyst/waEventExecutionTime.log
в следующем формате:
===Start log block=== Recorded: Имя пользователя array ( event_name => array ( 0 => array ( 'app_id' => 'crm', //приложение, в котором находится обработчик события 'plugin_id' => 'yandex', //плагин, который подписался на обработку события 'regex' => '/backend_header/', //название события в виде регулярного выражения 'file' => 'webasyst.backend_header.handler.php', //имя файла, если указано 'class' => 'crmWebasystBackend_headerHandler', //имя класса 'method' => array ( 0 => 'execute', //методы класса-обработчика ), 'execution_time' => 0.0188, //время выполнения обработчика в секундах ), ), ) ===End log block==="
См. также описание класса waEvent
.
Поиск событий
Бывает, что не удаётся быстро найти в документации то событие, обработка которого позволит вам решить определённую задачу. Найти нужное событие можно с помощью специальных методов класса SystemConfig
.
Эти методы нужно описать в файле wa-config/SystemConfig.class.php
. Они срабатывают до и после любого события.
-
eventHook
Срабатывает перед любым событием.
-
eventHookAfter
Срабатывает после любого события.
public function eventHook ($event_app_id, $name, $params)
Срабатывает перед любым событием.
Параметры
-
$event_app_id
Идентификатор приложения, чьё событие собирается сработать.
-
$name
Идентификатор события.
-
$params
Значения параметров, доступных в обработчике события.
Пример
// Сохранять в лог-файл информацию о событии и параметрах, доступных в его обработчике waLog::log( $event_app_id . ' . ' . $name . PHP_EOL . 'params: ' . wa_dump_helper($params, ref([]), true), 'events-before.log' );
public function eventHookAfter ($event_app_id, $name, $params, $result)
Срабатывает после любого события. Позволяет просматривать результаты, возвращённые всеми обработчиками.
Параметры
-
$event_app_id
Идентификатор приложения, чьё событие сработало.
-
$name
Идентификатор сработавшего события.
-
$params
Значения параметров, доступных в обработчике события.
-
$result
Результаты, возвращённые обработчиками события.
Пример
// Сохранять в лог-файл информацию о событии, параметрах, доступных в его обработчике, // и о результатах, возвращённых обработчиками waLog::log( $event_app_id . '.' . $name . PHP_EOL . 'params: ' . wa_dump_helper($params, ref([]), true) . PHP_EOL . 'result => ' . wa_dump_helper($result, ref([]), true), 'events-after.log' );