07 События модели
События и управление событиями
Модели позволяют реализовать события, которые будут срабатывать при выполнении вставки/обновления/удаления. Другими словами, события помогают определить бизнес-логику для определенной модели. Ниже приведены события, поддерживаемые 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\Db. Phalcon\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 запросы.