Обработка событий

Содержание...

Приложения, работающие на основе фреймворка Webasyst, могут поддерживать события. Событие — это место в коде приложения, в котором к приложению можно подключить дополнительный программный код, не внося изменения в само приложение. Дополнительный программный код может выполняться в другом программном продукте: в плагине или стороннем приложении. Должно ли ваше приложение поддерживать события, вы решаете самостоятельно.

Как это работает

Простой пример:

  1. Пользователь открывает страницу сайта.
  2. Для формирования HTML-кода страницы выполняется PHP-код приложения.
  3. В коде приложения есть специальное место — событие. Это место добавил разработчик приложения.
  4. Событие обрабатывается плагином — так решил разработчик плагина. Как только выполнение кода приложения дойдёт до события, сработает код плагина, который подключён к этому событию.

В обработке событий участвуют 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 аргумента:

  1. Параметры события. Это значение передаётся при вызове метода wa()->event('event_name', $params) в виде аргумента $params. Для разных событий в обработчики передаются разные параметры. В том числе могут не передаваться никакие параметры — в этом случае в обработчике события будет доступно значение аргумента null.

    Параметры, если они передаются, всегда доступны по ссылке. Поэтому их можно изменять в коде метода-обработчика, чтобы они могли дальше использоваться в приложении после обработки события, — если это предполагает логика работы приложения.
  2. Имя события. Его удобно использовать в обработчике, например, если один метод обрабатывает несколько событий, — чтобы изменять логику обработчика в зависимости от имени конкретного события. Этот аргумент ­необязательный — его можно не указывать в сигнатуре метода-обработчика, если это значение не планируется использовать.
Пример 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'
);