Поведение модели

Поведения — это некторые общие конструкции или компоненты, которые могут быть применены несколькими моделями в целях переиспользования кода. 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 для создания собственного поведения. A behavior must be a class implementing the Phalcon\Mvc\Model\BehaviorInterface. Also, Phalcon\Mvc\Model\Behavior provides most of the methods needed to ease the implementation of behaviors.

В качестве примера приведем следующее поведение, оно реализует поведение 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;
}