模型事件¶
概览¶
模型允许您在执行插入/更新/删除操作时实现事件,这些事件可用于定义业务规则。以下是支持的事件及其执行顺序:Phalcon\Mvc\Model和它们的执行顺序:
操作 | 名称 | 停止? | 解释 |
---|---|---|---|
插入 | afterCreate | 否 | 在创建记录后运行 |
删除 | afterDelete | 否 | 在删除记录后运行 |
获取 | afterFetch | 否 | 在获取记录后运行 |
插入/更新 | afterSave | 否 | 在保存记录后运行 |
更新 | afterUpdate | 否 | 在更新记录后运行 |
插入/更新 | afterValidation | 是 | 在字段验证为非空(非空字符串或外键)之后执行null /空字符串或外键 |
插入 | afterValidationOnCreate | 是 | 在字段验证为非空(非空字符串或外键)之后执行null /空字符串或外键在插入时 |
更新 | afterValidationOnUpdate | 是 | 在字段验证为非空(非空字符串或外键)之后执行null /空字符串或外键在更新时 |
插入 | beforeCreate | 是 | 在创建记录前运行 |
删除 | beforeDelete | 是 | 在删除记录前运行 |
插入/更新 | beforeSave | 是 | 在保存记录前运行 |
更新 | beforeUpdate | 是 | 在更新记录前运行 |
插入/更新 | beforeValidation | 是 | 在字段验证为非空(非空字符串或外键)之前执行null /空字符串或外键 |
插入 | beforeValidationOnCreate | 是 | 在字段验证为非空(非空字符串或外键)之前执行null /空字符串或外键在插入时 |
更新 | beforeValidationOnUpdate | 是 | 在字段验证为非空(非空字符串或外键)之前执行null /空字符串或外键在更新时 |
删除 | notDeleted | 否 | 在记录未被删除(失败)时运行 |
保存 | notSaved | 否 | 在记录未被保存(失败)时运行 |
插入/更新 | onValidationFails | 是 | 在完整性验证失败后执行 |
插入/更新 | prepareSave | 否 | 在保存前执行并允许数据操作 |
插入/更新 | validation | 是 | 在字段验证为非空(非空字符串或外键)之前执行 |
事件¶
模型作为事件管理器的监听者。因此,我们只需要在模型中直接实现上述事件作为公共方法:
<?php
namespace MyApp\Models;
use Phalcon\Mvc\Model;
/**
* Class Invoices
*
* @property string $inv_created_at
* @property int $inv_cst_id
* @property int $inv_id
* @property string $inv_number
* @property string $inv_title
* @property float $inv_total
*/
class Invoices extends Model
{
/**
* @var int
*/
public $inv_cst_id;
/**
* @var string
*/
public $inv_created_at;
/**
* @var int
*/
public $inv_id;
/**
* @var string
*/
public $inv_number;
/**
* @var string
*/
public $inv_title;
/**
* @var float
*/
public $inv_total;
public function beforeValidationOnCreate()
{
if ($this->inv_total < 1) {
$this->inv_total = 0;
}
}
}
事件可以用于在执行操作之前分配值,例如:
<?php
namespace MyApp\Models;
use Phalcon\Mvc\Model;
use function str_pad;
/**
* Class Invoices
*
* @property string $inv_created_at
* @property int $inv_cst_id
* @property int $inv_id
* @property string $inv_number
* @property string $inv_title
* @property float $inv_total
*/
class Invoices extends Model
{
/**
* @var int
*/
public $inv_cst_id;
/**
* @var string
*/
public $inv_created_at;
/**
* @var int
*/
public $inv_id;
/**
* @var string
*/
public $inv_number;
/**
* @var string
*/
public $inv_title;
/**
* @var float
*/
public $inv_total;
public function beforeCreate()
{
$date = date('YmdHis');
$customer = substr(
str_pad(
$this->inv_cst_id, 6, '0', STR_PAD_LEFT
),
-6
);
$this->inv_number = 'INV-' . $customer . '-' . $date;
}
}
自定义事件管理器¶
此外,该组件与Phalcon\Events\Manager集成,这意味着我们可以创建在触发事件时运行的监听者。
<?php
namespace MyApp\Models;
use Phalcon\Mvc\Model;
use Phalcon\Events\Manager;
/**
* Class Invoices
*
* @property string $inv_created_at
* @property int $inv_cst_id
* @property int $inv_id
* @property string $inv_number
* @property string $inv_title
* @property float $inv_total
*/
class Invoices extends Model
{
/**
* @var int
*/
public $inv_cst_id;
/**
* @var string
*/
public $inv_created_at;
/**
* @var int
*/
public $inv_id;
/**
* @var string
*/
public $inv_number;
/**
* @var string
*/
public $inv_title;
/**
* @var float
*/
public $inv_total;
public function initialize()
{
$eventsManager = new Manager();
$eventsManager->attach(
'model:beforeSave',
function (Event $event, $invoice) {
if ($invoice->inv_total < 1) {
return false;
}
return true;
}
);
$this->setEventsManager($eventsManager);
}
}
在上面给出的示例中,事件管理器仅充当对象和监听者(匿名函数)之间的桥梁。当Invoices
保存时会向监听者触发事件:
<?php
use MyApp\Models\Invoices;
$invoice = new Invoices();
$invoice->inv_cst_id = 10;
$invoice->inv_title = 'Invoice for ACME Inc.';
$invoice->save();
如果我们希望应用程序中创建的所有对象都使用相同的 EventsManager,则需要在 DI 容器中设置 Models Manager 时分配它:
<?php
use MyApp\Models\Invoices;
use Phalcon\Di\FactoryDefault;
use Phalcon\Events\Event;
use Phalcon\Events\Manager;
use Phalcon\Mvc\Model\Manager as ModelsManager;
$container = new FactoryDefault();
$container->setShared(
'modelsManager',
function () {
$eventsManager = new Manager();
$eventsManager->attach(
'model:beforeSave',
function (Event $event, $model) {
if (get_class($model) === Invoices::class) {
if ($model->inv_total < 1) {
return false;
}
}
return true;
}
);
$modelsManager = new ModelsManager();
$modelsManager->setEventsManager($eventsManager);
return $modelsManager;
}
);
如果监听者返回 false,这将停止当前正在执行的操作。
记录 SQL 语句¶
当使用高级抽象组件如Phalcon\Mvc\Model来访问数据库时,很难理解最终发送到数据库系统的语句是什么。Phalcon\Mvc\Model内部由Phalcon\Db. Phalcon\Logger\Logger与Phalcon\Db交互,提供数据库抽象层上的日志记录功能,从而允许我们记录发生的 SQL 语句。
<?php
use Phalcon\Db\Adapter\Pdo\Mysql;
use Phalcon\Di\FactoryDefault;
use Phalcon\Events\Manager;
use Phalcon\Logger\Logger;
use Phalcon\Logger\Adapter\Stream;
$container = new FactoryDefault();
$container->set(
'db',
function () {
$eventsManager = new Manager();
$adapter = new Stream('/storage/logs/db.log');
$logger = new Logger(
'messages',
[
'main' => $adapter,
]
);
$eventsManager->attach(
'db:beforeQuery',
function ($event, $connection) use ($logger) {
$logger->info(
$connection->getSQLStatement()
);
}
);
$connection = new Mysql\(
[
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'phalcon',
]
);
$connection->setEventsManager($eventsManager);
return $connection;
}
);
由于模型访问默认数据库连接,所有发送到数据库系统的 SQL 语句都将记录在文件中:
<?php
use MyApp\Models\Invoices;
$invoice = new Invoices();
$invoice->inv_cst_id = 10;
$invoice->inv_title = 'Invoice for ACME Inc.';
$invoice->inv_total = 10000;
if ($invoice->save() === false) {
echo 'Cannot save robot';
}
如上所述,文件/storage/logs/db.log将包含类似以下内容:
日志
[Mon, 30 Apr 12 13:47:18 -0500][DEBUG][Resource Id #77] INSERT INTO co_invoices
(inv_cst_id, inv_title, inv_total) VALUES (10, 'Invoice for ACME Inc.', 10000)
分析 SQL 语句¶
使用Phalcon\Db,这是Phalcon\Mvc\Model的底层组件,可以对 ORM 生成的 SQL 语句进行分析以评估数据库操作的性能。通过分析日志可以帮助识别 SQL 代码中的瓶颈:
<?php
use Phalcon\Db\Profiler;
use Phalcon\Di\FactoryDefault;
use Phalcon\Events\Manager;
use Phalcon\Db\Adapter\Pdo;
$container = new FactoryDefault();
$container->set(
'profiler',
function () {
return new Profiler();
},
true
);
$container->set(
'db',
function () use ($container) {
$manager = new Manager();
$profiler = $container->getProfiler();
$manager->attach(
'db',
function ($event, $connection) use ($profiler) {
if ($event->getType() === 'beforeQuery') {
$profiler->startProfile(
$connection->getSQLStatement()
);
}
if ($event->getType() === 'afterQuery') {
$profiler->stopProfile();
}
}
);
$connection = new Mysql(
[
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'phalcon',
]
);
$connection->setEventsManager($manager);
return $connection;
}
);
分析某些查询:
<?php
use MyApp\Models\Invoices;
Invoices::find();
Invoices::find(
[
'order' => 'inv_cst_id, inv_title',
]
);
Invoices::find(
[
'limit' => 30,
]
);
$profiles = $container->get('profiler')->getProfiles();
foreach ($profiles as $profile) {
echo 'SQL: ',
$profile->getSQLStatement(),
PHP_EOL,
'Start: ',
$profile->getInitialTime(),
PHP_EOL,
'Final: ',
$profile->getFinalTime(),
PHP_EOL,
'Elapsed: ',
$profile->getTotalElapsedSeconds(),
PHP_EOL
);
}
每个生成的分析记录包含每条指令完成所需的时间(以毫秒为单位),以及生成的 SQL 语句。