События и управление событиями

Модели позволяют реализовать события, которые будут срабатывать при выполнении вставки/обновления/удаления. Другими словами, события помогают определить бизнес-логику для определенной модели. Ниже приведены события, поддерживаемые Phalcon\Mvc\Model и порядок их выполнения:

ОперацияНазваниеМожет остановить операцию?Пояснение
Вставка afterCreate Нет Выполняется после выполнения требуемой операции над системой базы данных только при выполнении операции вставки
Удаление afterDelete Нет Выполняется после выполнения операции удаления
Обновление afterUpdate Нет Выполняется после выполнения требуемой операции в системе базы данных только при выполнении операции обновления
Вставка/Обновление afterSave Нет Выполняется после выполнения требуемой операции в системе базы данных
Вставка/Обновление afterValidation Да Выполняется после проверки полей на наличие не пустых / пустых строк или внешних ключей
Вставка afterValidationOnCreate Да Выполняется после проверки полей на наличие ненулевых / пустых строк или внешних ключей при выполнении операции вставки
Обновление afterValidationOnUpdate Да Выполняется после проверки полей на наличие ненулевых / пустых строк или внешних ключей при выполнении операции обновления
Вставка/Обновление beforeValidation Да Выполняется перед проверкой полей на наличие не пустых строк / нулей или внешних ключей
Вставка beforeCreate Да Выполняется перед требуемой операцией в системе базы данных только при выполнении операции вставки
Удаление beforeDelete Да Выполняется до выполнения операции удаления
Вставка/Обновление beforeSave Да Запускается до требуемой операции над системой базы данных
Обновление beforeUpdate Да Выполняется перед требуемой операцией в системе базы данных только при выполнении операции обновления
Вставка beforeValidationOnCreate Да Выполняется перед проверкой полей на наличие не пустых строк / нулей или внешних ключей при выполнении операции вставки
Обновление beforeValidationOnUpdate Да Выполняется до проверки поля на не нулевую/пустую строку или на внешние ключи при выполнении операции обновления
Вставка/Обновление onValidationFails Да (уже остановлено) Выполняется после сбоя средства проверки целостности
Вставка/Обновление validation Да Выполняется до проверки поля на не нулевую/пустую строку или на внешние ключи при выполнении операции обновления

Реализация событий в классе модели

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

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function beforeValidationOnCreate()
    {
        echo 'Это выполняется перед созданием робота!';
    }
}

События могут быть полезны для присвоения значений перед выполнением операции, например:

<?php

use Phalcon\Mvc\Model;

class Products extends Model
{
    public function beforeCreate()
    {
        // Установить дату создания
        $this->created_at = date('Y-m-d H:i:s');
    }

    public function beforeUpdate()
    {
        // Установить дату модификации
        $this->modified_in = date('Y-m-d H:i:s');
    }
}

Использование пользовательского менеджера событий

Кроме того, этот компонент интегрируется с Phalcon\Events\Manager, это означает, что мы можем создать слушателей, которые запускаются при срабатывании события.

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;

class Robots extends Model
{
    public function initialize()
    {
        $eventsManager = new EventsManager();

        // Добавляем анонимную функцию в качестве слушателя для событий "model"
        $eventsManager->attach(
            'model:beforeSave',
            function (Event $event, $robot) {
                if ($robot->name === 'Scooby Doo') {
                    echo "Scooby Doo isn't a robot!";

                    return false;
                }

                return true;
            }
        );

        // Устанавливаем менеджер событий для события
        $this->setEventsManager($eventsManager);
    }
}

В приведенном выше примере диспетчер событий действует только как мост между объектом и прослушивателем (анонимная функция). События будут запускаться в прослушиватель при сохранении robots:

<?php

use Store\Toys\Robots;

$robot = new Robots();

$robot->name = 'Scooby Doo';
$robot->year = 1969;

$robot->save();

Если мы хотим, чтобы все объекты, созданные в нашем приложении использовали один и тот же EventsManager, то мы должны назначить его менеджеру модели:

<?php

use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;

// Регистрация сервиса modelsManager
$di->setShared(
    'modelsManager',
    function () {
        $eventsManager = new EventsManager();

        // Добавляем анонимную функцию в качестве
        // слушателя для событий "model"
        $eventsManager->attach(
            'model:beforeSave',
            function (Event $event, $model) {
                // Перехватываем события, производимые моделью Robots
                if (get_class($model) === 'Store\Toys\Robots') {
                    if ($model->name === 'Scooby Doo') {
                        echo "Scooby Doo isn't a robot!";

                        return false;
                    }
                }

                return true;
            }
        );

        // Устанавливаем EventsManager по умолчанию
        $modelsManager = new ModelsManager();

        $modelsManager->setEventsManager($eventsManager);

        return $modelsManager;
    }
);

Если слушатель возвращает false, то это прерывает выполняемую операцию.

Логирование низкоуровневых SQL запросов

При использовании компонентов абстракции высокого уровня, таких как Phalcon\Mvc\Model, для доступа к базе данных, трудно понять, какие операции, в конечном итоге, посылаются базе данных. Phalcon\Mvc\Model поддерживается изнутри Phalcon\DbPhalcon\Logger взаимодействует с Phalcon\Db, обеспечивая возможность ведения логов на уровне абстракции базы данных, таким образом, позволяя нам логировать SQL запросы.

<?php

use Phalcon\Logger;
use Phalcon\Events\Manager;
use Phalcon\Logger\Adapter\File as FileLogger;
use Phalcon\Db\Adapter\Pdo\Mysql as Connection;

$di->set(
    'db',
    function () {
        $eventsManager = new EventsManager();

        $logger = new FileLogger('app/logs/debug.log');

        // Слушаем все события базы данных
        $eventsManager->attach(
            'db:beforeQuery',
            function ($event, $connection) use ($logger) {
                $logger->log(
                    $connection->getSQLStatement(),
                    Logger::INFO
                );
            }
        );

        $connection = new Connection(
            [
                'host'     => 'localhost',
                'username' => 'root',
                'password' => 'secret',
                'dbname'   => 'invo',
            ]
        );

        // Назначаем EventsManager экземпляру адаптера базы данных
        $connection->setEventsManager($eventsManager);

        return $connection;
    }
);

Как только модель взаимодействует с соединением, все SQL запросы, которые передаются в базу данных, будут сохранены в файле:

<?php

use Store\Toys\Robots;

$robot = new Robots();

$robot->name       = 'Robby the Robot';
$robot->created_at = '1956-07-21';

if ($robot->save() === false) {
    echo 'Не удалось сохранить робота';
}

Упомянутый выше файл app/logs/db.log будет содержать что-то вроде этого:

[Mon, 30 Apr 12 13:47:18 -0500][DEBUG][Resource Id #77] INSERT INTO robots (name, created_at) VALUES ('Robby the Robot', '1956-07-21')

Профилирование SQL запросов

Благодаря Phalcon\Db, основе компонента Phalcon\Mvc\Model, возможно профилировать SQL запросы, генерируемые ORM, в целях анализа производительности операций с базой данных. При этом вы можете диагностировать проблемы производительности и выявлять узкие места.

<?php

use Phalcon\Db\Profiler as ProfilerDb;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Db\Adapter\Pdo\Mysql as MysqlPdo;

$di->set(
    'profiler',
    function () {
        return new ProfilerDb();
    },
    true
);

$di->set(
    'db',
    function () use ($di) {
        $eventsManager = new EventsManager();

        // Получаем общий экземпляр DbProfiler
        $profiler = $di->getProfiler();

        // Слушаем все события базы данных
        $eventsManager->attach(
            'db',
            function ($event, $connection) use ($profiler) {
                if ($event->getType() === 'beforeQuery') {
                    $profiler->startProfile(
                        $connection->getSQLStatement()
                    );
                }

                if ($event->getType() === 'afterQuery') {
                    $profiler->stopProfile();
                }
            }
        );

        $connection = new MysqlPdo(
            [
                'host'     => 'localhost',
                'username' => 'root',
                'password' => 'secret',
                'dbname'   => 'invo',
            ]
        );

        // Назначаем EventsManager экземпляру адаптера базы данных
        $connection->setEventsManager($eventsManager);

        return $connection;
    }
);

Профилирование некоторых запросов:

<?php

use Store\Toys\Robots;

// Отправим некоторый SQL запрос в базу данных
Robots::find();

Robots::find(
    [
        'order' => 'name',
    ]
);

Robots::find(
    [
        'limit' => 30,
    ]
);

// Получим данные профилировщика
$profiles = $di->get('profiler')->getProfiles();

foreach ($profiles as $profile) {
   echo 'SQL запрос: ', $profile->getSQLStatement(), "\n";
   echo 'Начальное время: ', $profile->getInitialTime(), "\n";
   echo 'Конечное время: ', $profile->getFinalTime(), "\n";
   echo 'Общее истекшее время: ', $profile->getTotalElapsedSeconds(), "\n";
}

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