05 Менеджер событий
Менеджер событий
Цель этого компонента - перехватить выполнение большинства других компонентов структуры, создав «точки прерываний». Эти точки прерываний позволяют разработчику получать информацию о состоянии, манипулировать данными или изменять поток выполнения в процессе работы компонента.
Соглашение об именовании
События Phalcon используют пространства имен, чтобы избежать коллизий имен. Каждый компонент в Phalcon занимает другое пространство имен событий, и вы можете создавать свои собственные, как сочтете нужным. Имена событий форматируются как component:event. Например, поскольку Phalcon\Db занимает пространство имен db , полное имя события afterQuery - db:afterQuery.
При присоединении прослушивателей событий к менеджеру событий можно использовать component для перехвата всех событий из этого компонента (например. db , чтобы поймать все события Phalcon\Db) или component:event для конкретного события (например. db:afterQuery).
Пример использования
В следующем примере мы будем использовать EventManager для прослушивания события afterQuery, созданного в соединении MySQL, управляемого Phalcon\Db:
<?php
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Db\Adapter\Pdo\Mysql as DbAdapter;
$eventsManager = new EventsManager();
$eventsManager->attach(
    'db:afterQuery',
    function (Event $event, $connection) {
        echo $connection->getSQLStatement();
    }
);
$connection = new DbAdapter(
    [
        'host'     => 'localhost',
        'username' => 'root',
        'password' => 'secret',
        'dbname'   => 'invo',
    ]
);
// Назначить eventManager экземпляру адаптера db
$connection->setEventsManager($eventsManager);
// Отправить команду SQL на сервер базы данных
$connection->query(
    'SELECT * FROM products p WHERE p.status = 1'
);
Теперь каждый раз, когда запрос выполняется, оператор SQL будет выводиться. Первый параметр, передаваемый лямбда-функции, содержит контекстную информацию о запущенном событии, второй-источник события (в данном случае само соединение). Можно также указать третий параметр, который будет содержать произвольные данные, относящиеся к событию.
Вместо использования лямбда-функций можно использовать классы прослушивателя событий. Прослушиватели событий также позволяют прослушивать несколько событий. В этом примере мы реализуем Phalcon\Db\Profiler для обнаружения операторов SQL, выполнение которых занимает больше времени, чем ожидалось:
<?php
use Phalcon\Db\Profiler;
use Phalcon\Events\Event;
use Phalcon\Logger;
use Phalcon\Logger\Adapter\File;
class MyDbListener
{
    protected $profiler;
    protected $logger;
    /**
     * Создает профилировщик и запускает ведение журнала
     */
    public function __construct()
    {
        $this->profiler = new Profiler();
        $this->logger   = new Logger('../apps/logs/db.log');
    }
    /**
     * Это выполняется, если событие 'beforeQuery'
     */
    public function beforeQuery(Event $event, $connection)
    {
        $this->profiler->startProfile(
            $connection->getSQLStatement()
        );
    }
    /**
     * Это выполняется, если событие 'afterQuery'
     */
    public function afterQuery(Event $event, $connection)
    {
        $this->logger->log(
            $connection->getSQLStatement(),
            Logger::INFO
        );
        $this->profiler->stopProfile();
    }
    public function getProfiler()
    {
        return $this->profiler;
    }
}
Прикрепление прослушивателя событий к менеджеру событий так же просто, как:
<?php
// Создание прослушивателя базы данных
$dbListener = new MyDbListener();
// Прослушивание всех событий базы данных
$eventsManager->attach(
    'db',
    $dbListener
);
Полученные данные профиля можно получить из прослушивателя:
<?php
// Отправка команды SQL на сервер баз данных
$connection->execute(
    'SELECT * FROM products p WHERE p.status = 1'
);
foreach ($dbListener->getProfiler()->getProfiles() as $profile) {
    echo 'SQL Statement: ', $profile->getSQLStatement(), '\n';
    echo 'Start Time: ', $profile->getInitialTime(), '\n';
    echo 'Final Time: ', $profile->getFinalTime(), '\n';
    echo 'Total Elapsed Time: ', $profile->getTotalElapsedSeconds(), '\n';
}
Создание компонентов, запускающих события
В приложении можно создавать компоненты, которые инициируют события в EventsManager. Как следствие, могут существовать прослушиватели, реагирующие на эти события при генерации.В следующем примере мы создаем компонент MyComponent. Этот компонент поддерживает EventsManager (реализует Phalcon\Events\EventsAwareInterface); при выполнении метода someTask() он инициирует два события для любого прослушивателя в EventsManager:
<?php
use Phalcon\Events\EventsAwareInterface;
use Phalcon\Events\ManagerInterface;
class MyComponent implements EventsAwareInterface
{
    protected $eventsManager;
    public function setEventsManager(ManagerInterface $eventsManager)
    {
        $this->eventsManager = $eventsManager;
    }
    public function getEventsManager()
    {
        return $this->eventsManager;
    }
    public function someTask()
    {
        $this->eventsManager->fire('my-component:beforeSomeTask', $this);
        // Do some task
        echo 'Here, someTask\n';
        $this->eventsManager->fire('my-component:afterSomeTask', $this);
    }
}
Обратите внимание, что в этом примере используется пространство имен событий my-component. Теперь нам нужно создать прослушиватель событий для этого компонента:
<?php
use Phalcon\Events\Event;
class SomeListener
{
    public function beforeSomeTask(Event $event, $myComponent)
    {
        echo "Здесь, beforeSomeTask\n";
    }
    public function afterSomeTask(Event $event, $myComponent)
    {
        echo "Здесь, afterSomeTask\n";
    }
}
Теперь давайте заставим все работать вместе:
<?php
use Phalcon\Events\Manager as EventsManager;
// Создание менеджера событий
$eventsManager = new EventsManager();
// Создание экземпляра MyComponent
$myComponent = new MyComponent();
// Привязка eventsManager к экземпляру
$myComponent->setEventsManager($eventsManager);
// Присоединить слушателя к EventsManager
$eventsManager->attach(
    'my-component',
    new SomeListener()
);
// Выполнение методов в компоненте
$myComponent->someTask();
Как someTask() выполняется, два метода в слушателе будут выполнены, производя следующий вывод:
Here, beforeSomeTask Here, someTask Here, afterSomeTask
Дополнительные данные также могут передаваться при срабатывании события с помощью третьего параметра fire():
<?php
$eventsManager->fire('my-component:afterSomeTask', $this, $extraData);
В прослушиватель третий параметр также получает эти данные:
<?php
use Phalcon\Events\Event;
// Получение данных по третьему параметру
$eventsManager->attach(
    'my-component',
    function (Event $event, $component, $data) {
        print_r($data);
    }
);
// Получение данных из контекста события
$eventsManager->attach(
    'my-component',
    function (Event $event, $component) {
        print_r($event->getData());
    }
);
Использование сервисов DI
Расширяя Phalcon\Mvc\User\Plugin, вы можете получить доступ к службам из DI, как и в контроллере:
<?php
use Phalcon\Events\Event;
use Phalcon\Mvc\User\Plugin;
class SomeListener extends Plugin
{
    public function beforeSomeTask(Event $event, $myComponent)
    {
        echo 'Здесь, beforeSomeTask\n';
        $this->logger->debug(
            'beforeSomeTask has been triggered'
        );
    }
    public function afterSomeTask(Event $event, $myComponent)
    {
        echo 'Здесь, afterSomeTask\n';
        $this->logger->debug(
            'afterSomeTask has been triggered'
        );
    }
}
Распространение/аннулирование события
Многие слушатели могут быть добавлены к тому же менеджеру событий. Это означает, что для одного и того же типа событий многие слушатели могут быть уведомлены. Слушатели уведомляются в том порядке, в котором они были зарегистрированы в EventManager. Некоторые события отменены, что указывает на то, что они могут быть остановлены, чтобы другие слушатели не были уведомлены о событии:
<?php
use Phalcon\Events\Event;
$eventsManager->attach(
    'db',
    function (Event $event, $connection) {
        // We stop the event if it is cancelable
        if ($event->isCancelable()) {
            // Остановить событие, поэтому другие слушатели не будут уведомлены об этом
            $event->stop();
        }
        // ...
    }
);
По умолчанию события отменены - даже большинство событий, созданных фреймворком, являются отменными. Вы можете запустить событие без отмены, передав false в четвертом параметре fire():
<?php
$eventsManager->fire('my-component:afterSomeTask', $this, $extraData, false);
Приоритеты слушателя
При подключении слушателей вы можете установить определенный приоритет. С помощью этой функции вы можете подключить слушателей, указывающих порядок их вызова:
<?php
$eventsManager->enablePriorities(true);
$eventsManager->attach('db', new DbListener(), 150); // Больше приоритета
$eventsManager->attach('db', new DbListener(), 100); // Обычный приоритет
$eventsManager->attach('db', new DbListener(), 50);  // Менее приоритетный
Сбор ответов
Менеджер событий может собирать каждый ответ, возвращаемый каждым уведомленным слушателем. В этом примере объясняется, как это работает:
<?php
use Phalcon\Events\Manager as EventsManager;
$eventsManager = new EventsManager();
// Настройте диспетчер событий для сбора ответов
$eventsManager->collectResponses(true);
// Прикрепите слушателя
$eventsManager->attach(
    'custom:custom',
    function () {
        return 'first response';
    }
);
// Прикрепите слушателя
$eventsManager->attach(
    'custom:custom',
    function () {
        return 'second response';
    }
);
// Выполнить событие
$eventsManager->fire('custom:custom', null);
// Получить все собранные ответы
print_r($eventsManager->getResponses());
Приведенный выше пример производит:
Array ( [0] => first response [1] => second response )
Реализация собственного EventsManager
Интерфейс Phalcon\Events\ManagerInterface должен быть реализован, чтобы создать собственный EventManager, заменяющий тот, который предоставлен Phalcon.
Список событий
События, доступные в Phalcon:
| Компонент | Событие | 
|---|---|
| ACL | acl:afterCheckAccess | 
| ACL | acl:beforeCheckAccess | 
| Application | application:afterHandleRequest | 
| Application | application:afterStartModule | 
| Application | application:beforeHandleRequest | 
| Application | application:beforeSendResponse | 
| Application | application:beforeStartModule | 
| Application | application:boot | 
| Application | application:viewRender | 
| CLI | dispatch:beforeException | 
| Collection | afterCreate | 
| Collection | afterSave | 
| Collection | afterUpdate | 
| Collection | afterValidation | 
| Collection | afterValidationOnCreate | 
| Collection | afterValidationOnUpdate | 
| Collection | beforeCreate | 
| Collection | beforeSave | 
| Collection | beforeUpdate | 
| Collection | beforeValidation | 
| Collection | beforeValidationOnCreate | 
| Collection | beforeValidationOnUpdate | 
| Collection | notDeleted | 
| Collection | notSave | 
| Collection | notSaved | 
| Collection | onValidationFails | 
| Collection | validation | 
| Collection Manager | collectionManager:afterInitialize | 
| Console | console:afterHandleTask | 
| Console | console:afterStartModule | 
| Console | console:beforeHandleTask | 
| Console | console:beforeStartModule | 
| Db | db:afterQuery | 
| Db | db:beforeQuery | 
| Db | db:beginTransaction | 
| Db | db:createSavepoint | 
| Db | db:commitTransaction | 
| Db | db:releaseSavepoint | 
| Db | db:rollbackTransaction | 
| Db | db:rollbackSavepoint | 
| Dispatcher | dispatch:afterExecuteRoute | 
| Dispatcher | dispatch:afterDispatch | 
| Dispatcher | dispatch:afterDispatchLoop | 
| Dispatcher | dispatch:afterInitialize | 
| Dispatcher | dispatch:beforeException | 
| Dispatcher | dispatch:beforeExecuteRoute | 
| Dispatcher | dispatch:beforeDispatch | 
| Dispatcher | dispatch:beforeDispatchLoop | 
| Dispatcher | dispatch:beforeForward | 
| Dispatcher | dispatch:beforeNotFoundAction | 
| Loader | loader:afterCheckClass | 
| Loader | loader:beforeCheckClass | 
| Loader | loader:beforeCheckPath | 
| Loader | loader:pathFound | 
| Micro | micro:afterHandleRoute | 
| Micro | micro:afterExecuteRoute | 
| Micro | micro:beforeExecuteRoute | 
| Micro | micro:beforeHandleRoute | 
| Micro | micro:beforeNotFound | 
| Middleware | afterBinding | 
| Middleware | afterExecuteRoute | 
| Middleware | afterHandleRoute | 
| Middleware | beforeExecuteRoute | 
| Middleware | beforeHandleRoute | 
| Middleware | beforeNotFound | 
| Model | afterCreate | 
| Model | afterDelete | 
| Model | afterSave | 
| Model | afterUpdate | 
| Model | afterValidation | 
| Model | afterValidationOnCreate | 
| Model | afterValidationOnUpdate | 
| Model | beforeDelete | 
| Model | notDeleted | 
| Model | beforeCreate | 
| Model | beforeDelete | 
| Model | beforeSave | 
| Model | beforeUpdate | 
| Model | beforeValidation | 
| Model | beforeValidationOnCreate | 
| Model | beforeValidationOnUpdate | 
| Model | notSave | 
| Model | notSaved | 
| Model | onValidationFails | 
| Models Manager | modelsManager:afterInitialize | 
| Request | request:afterAuthorizationResolve | 
| Request | request:beforeAuthorizationResolve | 
| Router | router:beforeCheckRoutes | 
| Router | router:beforeCheckRoute | 
| Router | router:matchedRoute | 
| Router | router:notMatchedRoute | 
| Router | router:afterCheckRoutes | 
| Router | router:beforeMount | 
| View | view:afterRender | 
| View | view:afterRenderView | 
| View | view:beforeRender | 
| View | view:beforeRenderView | 
| View | view:notFoundView | 
| Volt | compileFilter | 
| Volt | compileFunction | 
| Volt | compileStatement | 
| Volt | resolveExpression | 
