模型事件¶
概览¶
模型允许您在执行插入/更新/删除操作时实现事件,这些事件可用于定义业务规则。以下是支持的事件及其执行顺序: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 语句。