Документация InstantCMS

для администраторов и разработчиков

Инструменты пользователя

Инструменты сайта


dev:controllers:hooks

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

Контроллеры в InstantCMS 2 могут обмениваться данными при помощи системы событий (хуков).

События используются для:

  • уведомления других контроллеров о действиях в текущем контроллере;
  • предоставления возможности другим контроллерам внести изменения в данные, с которыми работает текущий контроллер.

Каждый контроллер может генерировать события, прикладывая к ним какие-либо данные. Другие контроллеры могут эти события перехватывать, обрабатывать связанные данные, затем возвращать их обратно тому контроллеру, который вызвал событие.

Контроллер, который создает событие называется источником. Контроллер, который перехватывает событие называется слушателем. Каждое событие имеет ровно один источник в конкретный момент. Слушателей может быть один, несколько, либо не быть вообще.

Если у события несколько слушателей, то они обрабатывают приложенные к событию данные по-очереди. Первый слушатель получает массив с данными от источника, модифицирует их, затем передает дальше. Следующий слушатель получает уже модифицированный массив и добавляет в него собственные изменения. И так далее, по цепочке. Последний слушатель возвращает полученный после всех изменений массив обратно источнику. Такая схема называется последовательной.

Возможна также параллельная схема, когда все слушатели получают исходные (чистые) данные от источника напрямую, модифицируют их и возвращают обратно. В этом случае на выходе события будет не одна переменная, а массив с ответами каждого из слушателей отдельно.

Например, при удалении пользователя контроллер users (источник) создает событие user_delete, прикладывая к нему массив с информацией об удаляемом пользователе (id, e-mail и т.д.). Контроллер comments (слушатель) перехватывает событие user_delete, получает id пользователя из приложенного массива и удаляет все комментарии данного пользователя. Затем это же событие перехватывает контроллер content и удаляет все публикации. И так далее.

Другой пример - при добавлении комментария контроллер comments создает событие html_filter, передавая с ним текст комментария. Контроллер typograph перехватывает событие, получает текст комментария, вырезает из него запрещенную разметку, затем возвращает обратно в comments, где комментарий уже добавляется в базу.

Таким образом, механизм событий позволяет делать «плагины для плагинов», то есть расширять или дополнять возможности одного контроллера другим. При создании собственного компонента очень рекомендуется закладывать в него поддержку событий, чтобы другие разработчики могли добавлять функционал вашему компоненту без прямого вмешательства в его код.

Создание событий

Последовательная схема

Для создания событий в контроллере используется статический метод hook($event_name[, $data]) класса cmsEventsManager. Данный метод принимает два параметра – название события и приложенные данные.

$result_data = cmsEventsManager::hook('my_controller_event', $data);

Название события может включать имя компонента и состоять из латинских букв, цифр и знаков подчеркивания. На выходе метод hook() возвращает ту же переменную $data, что была ему передана вторым параметром. Однако, содержимое переменной может отличаться, поскольку может быть изменено слушателями, перехватившими событие.

Первый параметр - название события - может быть массивом событий. CMS обработает их последовательно.

$result_data = cmsEventsManager::hook(['my_controller_event_first', 'my_controller_event_second'], $data);

Пример вызова события из контроллера auth при логине пользователя на сайт:

//авторизуем пользователя
$logged_id  = cmsUser::login($email, $password, $remember);
 
//если авторизация прошла успешно, уведомляем другие контроллеры
//в уведомлении передаем id авторизованного пользователя
if ( $logged_id ){
    cmsEventsManager::hook('auth_login', $logged_id);
}

В данном примере результат выполнения hook() никуда не сохраняется, поскольку событие имеет только уведомительную цель.

Пример события из контроллера comments при добавлении комментария:

//получаем текст комментария из запроса
$content = $this->request->get('content');
 
//просим другие контроллеры отфильтровать текст от мусора
$content_html = cmsEventsManager::hook('html_filter', $content);

В этом примере контроллер comments ожидает что после обработки события ему вернут очищенный текст комментария, который будет сохранен в переменную $content_html. Такой подход позволяет использовать внешние фильтры, которые можно заменить или расширить (например, написать свой дополнительный фильтр, вырезающий из текста ругательные слова).

Параллельная схема

Иногда требуется передать событию одно значение, а на выходе получить несколько. Тогда используется параллельная схема, при которой ответ каждого слушателя возвращается в отдельном виде.

Для создания события по параллельной схеме используется статический метод hookAll($event_name[, $data]) класса cmsEventsManager. Он принимает те же параметры, что и простой метод hook(), рассмотренный ранее. Отличие лишь в возвращаемых результатах.

Метод hookAll() всегда возвращает массив, в котором каждый элемент содержит исходное значение $data, обработанное одним отдельным слушателем.

Рассмотрим различие последовательной и параллельной схемы на примере. Допустим, у нас есть контроллер source, в котором есть такой массив:

$data = array(
    'title' => 'Заголовок', 
    'text' => 'Текст'
);

И есть два контроллера dest1 и dest2, принимающих событие source_event. Контроллер dest1 меняет поле title получаемого в событии массива, а dest2 – поле text.

Вызываем событие source_event по последовательной схеме:

$result_data = cmsEventsManager::hook('source_event', $data);

Контроллеры dest1 и dest2 перехватят событие и на выходе мы будем иметь нечто подобное:

//$result_data:
array(
    'title' => 'Заголовок изменен в dest1',
    'text' => 'Текст изменен в dest2'
)

То есть, исходный массив обработан обоими слушателями по-очереди.

Теперь вызовем то же событие по параллельной схеме:

$result_data = cmsEventsManager::hookAll('source_event', $data);

Смотрим что на выходе:

//$result_data:
array(
    '0' => array(
        'title' => 'Заголовок изменен в dest1',
        'text' => 'Текст'
    ),
    '1' => array(
        'title' => 'Заголовок',
        'text' => 'Текст изменен в dest2'
    )
)

То есть теперь результат работы каждого из слушателей представлен в виде отдельного ответа.

Перехват событий

Манифест

:!: В версиях InstantCMS выше 2.14.1 этот файл не требуется, CMS формирует список событий по списку файлов хуков в директории hooks контроллера. Но для совместимости с более старыми версиями движка (если это актуально для вас) вы можете создавать этот файл.

Компонент должен уведомить систему о том, какие события он хочет перехватывать. Это делается с помощью файла манифеста, внутри которого возвращается массив со списком перехватываемых событий.

Манифест располагается в файле /system/controllers/название-компонента/manifest.php

Пример манифеста для компонента comments:

<?php
    // system/controllers/comments/manifest.php
    return array(
 
        'hooks' => array(
            'user_login',
            'user_notify_types',
            'user_delete',
            'user_tab_info',
            'user_tab_show'
        )
 
    );

:!: При включенном кешировании в настройках сайта манифесты кешируются. В этом случае, при обновлении манифеста необходимо очищать папку /cache/data/ в корне сайта. На время разработки кеширование рекомендуется отключить.

Обработка перехваченного события

Обработка событий осуществляется с помощью хуков. Хук – это разновидность внешнего действия, которое вызывается при перехвате события. Для каждого перехватываемого события создается собственный хук.

Хуки компонента хранятся в папке /system/controllers/название-компонента/hooks. Название файла хука совпадает с названием перехватываемого события. Например, для перехвата события user_delete компонентом comments используется хук, определенный в файле /system/controllers/comments/hooks/user_delete.php.

Внутри файла хука должен быть определен класс on{Имя компонента}{Название события}, наследуемый от системного класса cmsAction. Название события может состоять из нескольких слов, разделенных знаком подчеркивания. В названии класса эти слова пишутся слитно – каждое с большой буквы.

Пример определения класса для хука user_delete компонента comments:

class onCommentsUserDelete extends cmsAction { }

Внутри класса должен быть определен метод run(). Метод run() может иметь один входящий параметр – данные, передаваемые источником при создании события.

Пример хука user_delete компонента comments:

<?php
 
class onCommentsUserDelete extends cmsAction {
 
    // Значение $user передается при создании события
    // user_delete в компоненте users
    public function run($user){
 
	// Удаляем все комментарии пользователя
        $this->model->deleteUserComments($user['id']);
 
	// Передаем входящие данные дальше
        return $user;
 
    }
 
}

:!: Важное условие: метод run() хука должен обязательно вернуть обратно данные, полученные на входе (и, возможно, измененные самим хуком). Это необходимо для того, чтобы другие обработчики этого же события получили эти данные далее по цепочке.

Внутри метода run() доступны все методы и свойства самого контроллера (класса, определенного в frontend.php), через $this. Например, для обращения к модели контроллера можно использовать:

$this->model

Т.к. в версиях InstantCMS выше 2.14.1 файлы манифестов не нужны, а сами события регистрируются в базе данных, для некоторых хуков регистрация в БД не нужна. Например, для хуков планировщика. Для того, чтобы CMS не пыталась регистрировать такие хуки, необходимо указать свойство $disallow_event_db_register в классе хука.

Пример хука cron_publication компонента content:

<?php
 
class onContentCronPublication extends cmsAction {
 
    public $disallow_event_db_register = true;
 
    public function run($user){
        // Логика хука
    }
 
}

Получение списка слушателей

Вы можете узнать какие контроллеры перехватывают то или иное событие. Для этого служит статический метод getEventListeners($event_name) класса cmsEventsManager.

Например, получим список слушателей события user_delete:

$listeners = cmsEventsManager::getEventListeners('user_delete');

Массив $listeners будет содержать:

Array
(
    [0] => groups
    [1] => content
    [2] => messages
    [3] => photos
    [4] => wall
    [5] => images
    [6] => rating
    [7] => activity
    [8] => comments
)

Поиск событий

Чтобы узнать какие события генерирует интересующий вас компонент – просто сделайте поиск строки cmsEventsManager::hook или cmsEventsManager::hookAll по всем файлам внутри папки данного компонента. Так вы сможете найти все создаваемые события и посмотреть какие данные в них передаются.

Например, в редакторе NetBeans поиск по папке можно запустить выбрав папку в дереве слева и нажав Ctrl+Shift+F. В большинстве популярных редакторов используется аналогичный принцип и сочетание клавиш.

Или же, вы можете ознакомиться со списком всех событий по компонентам.


Вернуться к оглавлению

dev/controllers/hooks.txt · Последнее изменение: 08.04.2021 13:00 — fuze