Skip to content

Model Behaviors


Overview

Behaviors are shared constructs that several models may adopt in order to reuse code. Although you can use traits to reuse code, behaviors have several benefits that make them more appealing. Traits require you to use exactly the same field names for common code to work. Behaviors are more flexible.

The ORM provides an API to implement behaviors in your models. Also, you can use the events and callbacks as seen before as an alternative to implement behaviors.

A behavior must be added in the model initializer, a model can have zero or more behaviors:

<?php

use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Behavior\Timestampable;

class Invoices extends Model
{
    /**
     * @var int
     */
    public $inv_id;

    /**
     * @var string
     */
    public $inv_created_at;

    /**
     * @var int
     */
    public $inv_status_flag;

    /**
     * @var string
     */
    public $inv_title;

    public function initialize()
    {
        $this->addBehavior(
            new Timestampable(
                [
                    'beforeCreate' => [
                        'field'  => 'inv_created_at',
                        'format' => 'Y-m-d',
                    ],
                ]
            )
        );
    }
}

Built In

The following built-in behaviors are provided by the framework:

Name Description
SoftDelete Instead of permanently deleting a record it marks the record as deleted changing the value of a flag column
Timestampable Allows to automatically update a model's attribute saving the datetime when a record is created or updated

Timestampable

This behavior receives an array of options, the first level key must be an event name indicating when the column must be assigned:

<?php

use Phalcon\Mvc\Model\Behavior\Timestampable;

public function initialize()
{
    $this->addBehavior(
        new Timestampable(
            [
                'beforeCreate' => [
                    'field'  => 'inv_created_at',
                    'format' => 'Y-m-d',
                ],
            ]
        )
    );
}

Each event can have its own options, field is the name of the column that must be updated, if format is a string it will be used as the format of the date function. format can also be an anonymous function offering additional functionality to generate any kind of timestamp string:

<?php

use DateTime;
use DateTimeZone;
use Phalcon\Mvc\Model\Behavior\Timestampable;

public function initialize()
{
    $this->addBehavior(
        new Timestampable(
            [
                'beforeCreate' => [
                    'field'  => 'inv_created_at',
                    'format' => function () {
                        $datetime = new Datetime(
                            new DateTimeZone('Europe/Stockholm')
                        );

                        return $datetime->format('Y-m-d H:i:sP');
                    },
                ],
            ]
        )
    );
}

If the option format is omitted a timestamp using the PHP's function time, will be used.

SoftDelete

This behavior can be used as follows:

<?php

use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Behavior\SoftDelete;

class Invoices extends Model
{
    const ACTIVE   = 1;
    const INACTIVE = 0;

    /**
     * @var int
     */
    public $inv_id;

    /**
     * @var string
     */
    public $inv_created_at;

    /**
     * @var int
     */
    public $inv_deleted_flag;

    /**
     * @var string
     */
    public $inv_title;

    public function initialize()
    {
        $this->addBehavior(
            new SoftDelete(
                [
                    'field' => 'inv_deleted_flag',
                    'value' => Invoices::INACTIVE,
                ]
            )
        );
    }
}

This behavior accepts two options: field and value, field determines what field must be updated, and value is the value to be deleted. Assuming that our table has the following rows:

mysql> select * from co_invoices;
+--------+------------------+-----------------------------+
| inv_id | inv_deleted_flag | inv_title                   |
+--------+------------------+-----------------------------+
|  1     | 0                | Invoice for ACME Inc.       |
|  2     | 0                | Invoice for Spaceballs Inc. |
+--------+------------------+-----------------------------+
2 rows in set (0.00 sec)

If we delete any of the two records the status will be updated instead of delete the record:

<?php

Invoices::findFirst(2)->delete();

The operation will result in the following data in the table:

mysql> select * from co_invoices;
+--------+------------------+-----------------------------+
| inv_id | inv_deleted_flag | inv_title                   |
+--------+------------------+-----------------------------+
|  1     | 0                | Invoice for ACME Inc.       |
|  2     | 1                | Invoice for Spaceballs Inc. |
+--------+------------------+-----------------------------+
2 rows in set (0.00 sec)

NOTE

You will need to ensure to specify the deleted condition to filter your records so that you can get deleted or not deleted results back. This behavior does not support automatic filtering.

NOTE

Adding this behaviour to a model prevents its afterDelete event from being triggered, since the record isn't actually deleted.

Custom

The ORM provides an API to create your own behaviors. A behavior must be a class implementing the Phalcon\Mvc\Model\BehaviorInterface or extend Phalcon\Mvc\Model\Behavior which exposes most of the methods required for implementing custom behaviors.

The Phalcon\Mvc\Model\BehaviorInterface requires two methods to be present in your custom behavior:

public function missingMethod(
    ModelInterface $model, 
    string $method, 
    array $arguments = []
)

This method acts as a fallback when a missing method is called on the model

public function notify(
    string $type, 
    ModelInterface $model
)

This method receives the notifications from the Events Manager.

Additionally, if you extend Phalcon\Mvc\Model\Behavior, you have access to:

Method Description
getOptions(string $eventName = null) Returns the behavior options related to an event
mustTakeAction(string $eventName) Checks whether the behavior must take action on certain event

The following behavior is an example, it implements the Blameable behavior which helps identify the user that is performing operations on a model:

<?php

use Phalcon\Di\Di;
use Phalcon\Mvc\ModelInterface;
use Phalcon\Mvc\Model\Behavior;

class Blameable extends Behavior
{
    public function notify(string $eventType, ModelInterface $model)
    {
        $container = Di::getDefault();
        $userName  = $container->get('auth')->getFullName();

        switch ($eventType) {

            case 'afterCreate':
            case 'afterDelete':
            case 'afterUpdate':

                file_put_contents(
                    'logs/blamable-log.txt',
                    $userName . ' ' . $eventType . ' ' . $model->inv_id
                );

                break;

            default:
                // ...
        }
    }
}

The above is a very simple behavior, but it illustrates how to create a behavior. Adding the behavior to a model is illustrated below:

<?php

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public function initialize()
    {
        $this->addBehavior(
            new Blameable()
        );
    }
}

A behavior is also capable of intercepting missing methods on your models, and offering functionality for them:

<?php

use Phalcon\Tag;
use Phalcon\Mvc\ModelInterface;
use Phalcon\Mvc\Model\Behavior;
use Phalcon\Mvc\Model\BehaviorInterface;

class Sluggable extends Behavior
{
    public function missingMethod(
        ModelInterface $model,
        string $method,
        $arguments = []
    ) {
        if ($method === 'getSlug') {
            return Tag::friendlyTitle($model->title);
        }
    }
}

Calling that method on a model that implements Sluggable returns a SEO friendly title:

<?php

$title = $invoice->getSlug();

Traits

You can use Traits to re-use code in your classes, this is another way to implement custom behaviors. The following trait implements a simple version of the Timestampable behavior:

<?php

trait Timestampable
{
    public function beforeCreate()
    {
        $this->inv_created_at = date('r');
    }

    public function beforeUpdate()
    {
        $this->inv_updated_at = date('r');
    }
}

Then you can use it in your model as follows:

<?php

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    use Timestampable;
}

NOTE

You can use traits instead of behaviors, but they do require that all your fields, that the behavior will affect, must have the same name. Also, if you implement an event method in a trait (e.g. beforeCreate) you cannot have it in your model since the two will produce an error.