Eventos del Modelo


Resumen

Los modelos le permiten implementar eventos que se lanzarán al realizar un insert/update/delete, que se pueden usar para definir las reglas de negocio. The following are the events supported by Phalcon\Mvc\Model and their order of execution:

Operación Nombre Detiene? Explicación
Insertar afterCreate No Se ejecuta después de crear un registro
Eliminar afterDelete No Se ejecuta después de borrar registros
Obtener afterFetch No Se ejecuta después de obtener registros
Insertar/Actualizar afterSave No Se ejecuta después de guardar un registro
Actualizar afterUpdate No Se ejecuta después de actualizar un registro
Insertar/Actualizar afterValidation Si Se ejecuta después de que se validen los campos no nulos/cadenas vacías o llaves ajenas
Insertar afterValidationOnCreate Si Se ejecuta después de que se validen los campos no nulos/cadenas vacías o llaves ajenas en una inserción
Actualizar afterValidationOnUpdate Si Se ejecuta después de que se validen los campos no nulos/cadenas vacías o claves ajenas en una actualización
Insertar beforeCreate Si Se ejecuta antes de crear un registro
Eliminar beforeDelete Si Se ejecuta antes de eliminar registros
Insertar/Actualizar beforeSave Si Se ejecuta antes de guardar un registro
Actualizar beforeUpdate Si Se ejecuta antes de actualizar un registro
Insertar/Actualizar beforeValidation Si Se ejecuta antes de que se validen los campos no nulos/cadenas vacías o claves ajenas
Insertar beforeValidationOnCreate Si Se ejecuta antes de que se validen los campos no nulos/cadenas vacías o claves ajenas
Actualizar beforeValidationOnUpdate Si Se ejecuta antes de que se validen los campos no nulos/cadenas vacías o claves ajenas en una actualización
Eliminar notDeleted No Se ejecuta cuando los registros no se eliminan (falla)
Guardar notSaved No Se ejecuta cuando los registros no se guardan (falla)
Insertar/Actualizar onValidationFails Si Se ejecuta después de que un validador de integridad falle
Insertar/Actualizar prepareSave No Se ejecuta antes de guardar y permite manipular datos
Insertar/Actualizar validation Si Se ejecuta antes de que se validen los campos no nulos/cadenas vacías o claves ajenas en una actualización

Eventos

Los modelos actúan como oyentes del gestor de eventos. Por lo tanto, sólo necesitamos implementar los eventos anteriores directamente en los modelos como métodos públicos:

<?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;
        }
    }
}

Los eventos se pueden usar para asignar valores antes de ejecutar una operación, por ejemplo:

<?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;
    }
}

Gestor de Eventos Personalizado

Additionally, this component is integrated with Phalcon\Events\Manager, this means we can create listeners that run when an event is triggered.

<?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);
    }
}

En el ejemplo anterior, el Gestor de Eventos sólo actúa como puente entre un objeto y un oyente (la función anónima). Los eventos se dispararán hacia el oyente cuando se guarde Invoices:

<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();
$invoice->inv_cst_id = 10;
$invoice->inv_title = 'Invoice for ACME Inc.';

$invoice->save();

Si queremos que todos los objetos creados en nuestra aplicación usen el mismo Gestor de Eventos, necesitamos asignarlo al Gestor de Modelos cuando lo configuramos en el contenedor DI:

<?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;
    }
);

Si un oyente devuelve false, detendrá la operación que se está ejecutando actualmente.

Registrar Sentencias SQL

When using high-level abstraction components such as Phalcon\Mvc\Model to access a database, it is difficult to understand which statements are finally sent to the database system. Phalcon\Mvc\Model is supported internally by Phalcon\Db. Phalcon\Logger interacts with Phalcon\Db, providing logging capabilities on the database abstraction layer, thus allowing us to log SQL statements as they happen.

<?php

use Phalcon\Db\Adapter\Pdo\Mysql;
use Phalcon\Di\FactoryDefault;
use Phalcon\Events\Manager;
use Phalcon\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;
    }
);

Como los modelos acceden a la conexión de base de datos por defecto, todas las sentencias SQL que se envían al sistema de base de datos se registran en el fichero:

<?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';
}

As above, the file /storage/logs/db.log will contain something like this:

[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)

Perfilando sentencias SQL

Using the Phalcon\Db, the underlying component of Phalcon\Mvc\Model, it is possible to profile the SQL statements generated by the ORM in order to analyze the performance of database operations. Analizar los registros ayudará a identificar cuellos de botella en su código 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',
            ]
        );

        // Assign the eventsManager to the db adapter instance
        $connection->setEventsManager($manager);

        return $connection;
    }
);

Perfilando algunas consultas:

<?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
    );
}

Cada perfil generado contiene la duración en milisegundos que cada instrucción consume para completarse, así como también la sentencia SQL generada.