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 запросы.