Поведения — это некторые общие конструкции или компоненты, которые могут быть применены несколькими моделями в целях переиспользования кода. ORM предоставляет API для реализации поведений для вашей модели. Кроме того, вы можете использовать события и функции обратного вызова, как видели раньше, в качестве альтернативы для более свободной реализации поведения.

Поведение должно быть добавлено при инициализации модели, модель может иметь ноль или более поведений:

<?php

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

class Users extends Model
{
    public $id;

    public $name;

    public $created_at;

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

Фреймворком предоставлены следующие встроенные поведения:

НазваниеОписание
Timestampable Позволяет автоматически обновлять атрибут модели, сохраняя дату и время, когда запись создается или обновляется
SoftDelete Вместо окончательного удаления записи, изменением значения флага столбца она помечается как удалённая

Timestampable

Это поведение в качестве аргумента принимает массив, ключи которого являются названиями событий, указывающих на то, когда должно происходить присваивание:

<?php

use Phalcon\Mvc\Model\Behavior\Timestampable;

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

Каждое событие может иметь свои собственные настройки, field — имя столбца, который необходимо обновить. Если format является строкой, то будет использоваться в качестве формата PHP функции date, format также может быть анонимной функцией, позволяющей вам свободно создавать любые виды временных меток:

<?php

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

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

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

Если опция format опущена, то будет использована временная метка PHP функции time.

SoftDelete

Это поведение может быть использовано следующим образом:

<?php

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

class Users extends Model
{
    const DELETED     = 'D';
    const NOT_DELETED = 'N';

    public $id;
    public $name;
    public $status;

    public function initialize()
    {
        $this->addBehavior(
            new SoftDelete(
                [
                    'field' => 'status',
                    'value' => Users::DELETED,
                ]
            )
        );
    }
}

Это поведение принимает две опции: field и value. Опция field указывает поле, которое должно быть обновлено, и value — значение, которым будут помечаться удаленные записи. Давайте представим, что таблица users имеет следующие данные:

mysql> select * from users;
+----+---------+--------+
| id | name    | status |
+----+---------+--------+
|  1 | Яна     | N      |
|  2 | Филипп  | N      |
+----+---------+--------+
2 rows in set (0.00 sec)

Если мы удалим любую из двух записей, изменится статус вместо удаления записи:

<?php

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

Операция приводит к следующим данным в таблице:

mysql> select * from users;
+----+---------+--------+
| id | name    | status |
+----+---------+--------+
|  1 | Яна     | N      |
|  2 | Филипп  | D      |
+----+---------+--------+
2 rows in set (0.00 sec)

Обратите внимание, что вам необходимо самостоятельно указывать в запросах условие удаления записи для того, чтобы игнорировать их как удаленные. Подобная логика не поддерживается поведением.

Создание собственных поведений

ORM предоставляет API для создания собственного поведения. Поведение должно быть классом, реализующим Phalcon\Mvc\Model\BehaviorInterface. Кроме того, Phalcon\Mvc\Model\Behavior предоставляет большую часть методов, необходимых для простой реализации поведения.

В качестве примера приведем следующее поведение, оно реализует поведение Blameable, которое помогает идентифицировать пользователя, выполняющего операции с моделью:

<?php

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

class Blameable extends Behavior implements BehaviorInterface
{
    public function notify($eventType, $model)
    {
        switch ($eventType) {

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

                $userName = // ... получаем текущего пользователя из сессии

                // Сохраняем в логах имя пользователя, тип события и идентификатор записи
                file_put_contents(
                    'logs/blamable-log.txt',
                    $userName . ' ' . $eventType . ' ' . $model->id
                );

                break;

            default:
                /* игнорируем остальные события */
        }
    }
}

Пример выше довольно прост, но он показывает, как создать поведение. Теперь давайте добавим его в модель:

<?php

use Phalcon\Mvc\Model;

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

Поведение также может перехватывать отсутствующие методы ваших моделей:

<?php

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

class Sluggable extends Behavior implements BehaviorInterface
{
    public function missingMethod($model, $method, $arguments = [])
    {
        // Если метод — 'getSlug', то преобразуем заголовок
        if ($method === 'getSlug') {
            return Tag::friendlyTitle($model->title);
        }
    }
}

Вызов этого метода у модели, реализующей Sluggable, возвращает SEO-оптимизированный заголовок:

<?php

$title = $post->getSlug();

Использование трейтов, как поведений

Начиная с PHP 5.4 вы можете использовать трейты, чтобы повторно использовать код в ваших классах. Это еще один способ для реализации пользовательского поведения. Следующий трейт реализует простой вариант поведения Timestampable:

<?php

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

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

Затем вы можете использовать его в вашей модели следующим образом: 

<?php

use Phalcon\Mvc\Model;

class Products extends Model
{
    use MyTimestampable;
}