🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # 模型事件 ## 事件与事件管理 模型允许您实现在执行插入/更新/删除时将抛出的事件,这些事件可用于定义业务规则。以下是 `Phalcon\Mvc\Model` 支持的事件及其执行顺序: | 操作 | 名称 | Can stop operation? | 说明 | | ------------------ | ------------------------ |:---------------------:| --------------------------------------------------------------------------------------------------------------------------------- | | Inserting | afterCreate | NO | 仅在进行插入操作时,在数据库系统上执行所需操作后运行 | | Deleting | afterDelete | NO | 删除操作完成后运行 | | Updating | afterUpdate | NO | 仅在进行更新操作时,在数据库系统上执行所需操作后运行 | | Inserting/Updating | afterSave | NO | 在数据库系统上执行所需的操作后运行 | | Inserting/Updating | afterValidation | YES | 在为非空/空字符串或外键验证字段后执行 | | Inserting | afterValidationOnCreate | YES | 在进行插入操作时,在字段验证非空/空字符串或外键之后执行 | | Updating | afterValidationOnUpdate | YES | 在进行更新操作时,在对字段进行非空/空字符串或外键验证之后执行 | | Inserting/Updating | beforeValidation | YES | 在字段验证非空/空字符串或外键之前执行| | Inserting | beforeCreate | YES | 仅在进行插入操作时才在数据库系统上执行所需操作之前运行 | | Deleting | beforeDelete | YES | 在执行删除操作之前运行 | | Inserting/Updating | beforeSave | YES |在数据库系统上执行所需的操作之前运行 | | Updating | beforeUpdate | YES | 仅在进行更新操作时才在数据库系统上执行所需操作之前运行 | | Inserting | beforeValidationOnCreate | YES | 在进行插入操作时,在字段验证非空值/空字符串或外键之前执行 | | Updating | beforeValidationOnUpdate | YES | 在进行更新操作时,在字段验证非空值/空字符串或外键之前执行 | | Inserting/Updating | onValidationFails | YES (already stopped) | 在完整性验证程序失败后执行 | | Inserting/Updating | validation | YES | 在进行更新操作时,在字段验证非空值/空字符串或外键之前执行 | ### 在Model的类中实现事件 使模型对事件做出反应的更简单方法是在模型的类中实现与事件名称相同的方法: ```php <?php namespace Store\Toys; use Phalcon\Mvc\Model; class Robots extends Model { public function beforeValidationOnCreate() { echo 'This is executed before creating a Robot!'; } } ``` 事件可用于在执行操作之前分配值,例如: ```php <?php use Phalcon\Mvc\Model; class Products extends Model { public function beforeCreate() { // Set the creation date $this->created_at = date('Y-m-d H:i:s'); } public function beforeUpdate() { // Set the modification date $this->modified_in = date('Y-m-d H:i:s'); } } ``` ### 使用自定义事件管理器 此外,该组件与`Phalcon\Events\Manager`集成,这意味着我们可以创建在触发事件时运行的侦听器。 ```php <?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(); // Attach an anonymous function as a listener for 'model' events $eventsManager->attach( 'model:beforeSave', function (Event $event, $robot) { if ($robot->name === 'Scooby Doo') { echo "Scooby Doo isn't a robot!"; return false; } return true; } ); // Attach the events manager to the event $this->setEventsManager($eventsManager); } } ``` 在上面给出的示例中,事件管理器仅充当对象和侦听器(匿名函数)之间的桥梁。保存`robots`时,将触发监听事件: ```php <?php use Store\Toys\Robots; $robot = new Robots(); $robot->name = 'Scooby Doo'; $robot->year = 1969; $robot->save(); ``` 如果我们希望在我们的应用程序中创建的所有对象使用相同的EventsManager,那么我们需要将它分配给Models Manager: ```php <?php use Phalcon\Events\Event; use Phalcon\Events\Manager as EventsManager; // Registering the modelsManager service $di->setShared( 'modelsManager', function () { $eventsManager = new EventsManager(); // Attach an anonymous function as a listener for 'model' events $eventsManager->attach( 'model:beforeSave', function (Event $event, $model) { // Catch events produced by the Robots model if (get_class($model) === 'Store\Toys\Robots') { if ($model->name === 'Scooby Doo') { echo "Scooby Doo isn't a robot!"; return false; } } return true; } ); // Setting a default 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 <?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'); // Listen all the database events $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', ] ); // Assign the eventsManager to the db adapter instance $connection->setEventsManager($eventsManager); return $connection; } ); ``` 当模型访问默认数据库连接时,发送到数据库系统的所有SQL语句都将记录在文件中: ```php <?php use Store\Toys\Robots; $robot = new Robots(); $robot->name = 'Robby the Robot'; $robot->created_at = '1956-07-21'; if ($robot->save() === false) { echo 'Cannot save robot'; } ``` 如上所述,文件*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`的底层组件,因此可以分析ORM生成的SQL语句,以分析数据库操作的性能。通过这种方式,您可以诊断性能问题并发现瓶颈。 ```php <?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(); // Get a shared instance of the DbProfiler $profiler = $di->getProfiler(); // Listen all the database events $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', ] ); // Assign the eventsManager to the db adapter instance $connection->setEventsManager($eventsManager); return $connection; } ); ``` 分析一些查询: ```php <?php use Store\Toys\Robots; // Send some SQL statements to the database Robots::find(); Robots::find( [ 'order' => 'name', ] ); Robots::find( [ 'limit' => 30, ] ); // Get the generated profiles from the profiler $profiles = $di->get('profiler')->getProfiles(); foreach ($profiles 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"; } ``` 每个生成的配置文件都包含每条指令完成所需的持续时间(以毫秒为单位)以及生成的SQL语句。