Doctrine ORM бихейверы, или как использовать трейты эфективно

С версии 5.4 php становиться более стабильным, мы решили поэкспериментировать с  “traits” и их использованием в реальных условиях.

Давайте посмотрим как их можно использовать с Doctrine2 сущьностями.

Трейты

Трейты в php являются лиш ветками свойств и методов которые вы можете скопировать в клас.
Все это делается на уровне интерпретатора и полностью прозрачно для Doctrine.

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

Обычные бихейверы

На пример на общих запросах для автоматического проставления дат для сущьностей, используя created_at и updated_at свойства даты.

Это то что применимо клюбому типу сущьности.

Это правило хорошего тона использование горизонтального повторного использования.
Это также момент, когда вы спрашиваете себя: “Как я могу не повторяться?”

Введение в бихейвор Timestampable

Timestampable это простой трейт который вы применяете к сущьности doctrine:

use DoctrineORMMapping as ORM;
use KnpDoctrineBehaviorsORM as ORMBehaviors;

/**
* @ORMEntity
*/
class Category
{
    use ORMBehaviorsTimestampableTimestampable;

    /**
     * @ORMId
     * @ORMColumn(type="integer")
     * @ORMGeneratedValue(strategy="NONE")
     */
    protected $id;
}

Обратите внимание на объявление use в теле класса.

Это добавит два свойства doctrine тип DateTime и два публичных метода к сущьности, вы получите поля createdAt и updatedAt:

$category = new Category;
$entityManager->persist($category);
$category->getCreatedAt();
$category->getUpdatedAt();

Как только вы обновите сущьности, getUpdatedAt вернет новую дату - дату и время обновления сущьности.

Установка

Timestampable и другие трейты доступны в пакете в репозитории KNP Labs/DoctrineBehaviors на github.

Вы можете легко установить их используя composer.

Просто добавьте это в файл composer.json в корневой директьории вашего проекта.

{
    "require": {
        "knplabs/doctrine-behaviors": "dev-master",
    }
}

Потом запустите composer:

curl -s http://getcomposer.org/installer | php
php composer.phar install

Слушатели (Listeners)

Все это возможно благодаря слушателям Doctrine, которые слушают события сохранения или обновления для каждой сущьности что использует Timestampable.

Но, что бы это заработало, вам необходимо зарегистрировать их.

Используя Symfony2, это действительно легко! Просто импортируйте файл определений сервисов:

# app/config/config.yml

imports:
    - { resource: ../../vendor/knplabs/doctrine-behaviors/config/orm-services.yml }

Translatable бихейвор

Действительно простое требование это сделать сущьность переводимой. Мы постаралисть сделать это как можно проще благодаря простому и логичному соглашению именования.

Для того, чтобы иметь рабочую translatable сущьность, пройдите эти 2 шага:

− используйте трайт Translatable:

use DoctrineORMMapping as ORM;
use KnpDoctrineBehaviorsORM as ORMBehaviors;

/**
* @ORMEntity
*/
class Category
{
    use ORMBehaviorsTranslatableTranslatable;

    /**
     * @ORMId
     * @ORMColumn(type="integer")
     * @ORMGeneratedValue(strategy="NONE")
     */
    protected $id;
}

− Определите сущьность CategoryTranslation с помощью трайта Translation:

<?php

use DoctrineORMMapping as ORM;

use KnpDoctrineBehaviorsORM as ORMBehaviors;

/**
* @ORMEntity
*/
class CategoryTranslation
{
    use ORMBehaviorsTranslatableTranslation;

    /**
     * @ORMColumn(type="string")
     */
    protected $name;
}

Вот и все!

TranslatableListener обнаружит связь между этими двумя сущьностями без необходимости вам вмешиваться.

Все, что вам нужно сделать сейчас, это поработать с ним через связь OneToMany, а это  значит что вы, на пример, можете легко сделать left join ваших переводов.

<?php

$category = new Category;
$category->translate('fr')->setName('Chaussures');
$category->translate('en')->setName('Shoes');
$em->persist($category);

$category->translate('en')->getName();

Tree

Tree использует материализованное реализацию пути для представления в виде деревьев.

Все узлы содержат их полный путь от их корня:

| id  | name       | path       |
+-----+------------+------------+
| 1   | fr         | /1         |
| 2   | villes     | /1/2       |
| 4   | subNantes  | /1/2/3/4   |
| 7   | en         | /7         |
| 8   | villes     | /7/8       |
| 9   | Nantes     | /7/8/9     |
| 10  | subNantes  | /7/8/9/10  |
| 11  | Lorient    | /7/8/11    |
| 12  | Rouen      | /7/8/12    |
| 6   | Rouen      | /1/2/6     |
| 3   | Nantes     | /1/2/3     |
| 5   | Lorient    | /1/2/5     |

Что бы представить ваши сукщьногсти в виде дерева, все что вам надо сделать это использовать трайт TreeNode:

use DoctrineORMMapping as ORM;
use KnpDoctrineBehaviorsORM as ORMBehaviors;

/**
* @ORMEntity(repositoryClass="CategoryRepository")
*/
class Category
{
    use ORMBehaviorsTreeNode;

    /**
     * @ORMId
     * @ORMColumn(type="integer")
     * @ORMGeneratedValue(strategy="NONE")
     */
    protected $id;
}

Вы так же должны использовать трайт TreeTree на сответствующей EntityRepository:

use DoctrineORMEntityRepository;
use KnpDoctrineBehaviorsORM as ORMBehaviors;

class CategoryRepository extends EntityRepository
{
    use ORMBehaviorsTreeTree;
}

Эта сущьность теперь имеет мощьный api для управления своими наследниками, родителями, переносить их, …

$root = $em->getRepository('Category')->getTree();

$root->getParentNode();
$root->getChildren();
$root[0][1]; // array access of children
$root->isLeafNode();
$root->isRootNode();