Менеджер событий

Цель этого компонента - перехватить выполнение большинства других компонентов структуры, создав «точки прерываний». Эти точки прерываний позволяют разработчику получать информацию о состоянии, манипулировать данными или изменять поток выполнения в процессе работы компонента.

Соглашение об именовании

События 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