Как стать автором
Обновить

Пользовательские репозитории в ORM Doctrine 2

Время на прочтение 5 мин
Количество просмотров 18K
В большинстве случаев стандартные методы, генерируемые доктриной на основе Yaml (XML или аннотаций), хватает только на получение каких то полей по какому-то простому фильтру. Для более сложного запроса приходиться пользоваться нативным QueryBuilder'ом и обращаться через dql запросы к нашей модели. Все это является следствием нагромождения больших кусков кода, которые имеют свойства дублироваться там где требуется применить идентичные запросы. А как хотелось бы обращаться с моделью просто и красиво через один единственный метод? Как? Напишем свой!

Пользовательские репозитории


Нам помогут пользовательские репозитории! Если коротко, то смысл этих репозиториев в том, что бы хранить в себе пользовательские (нестандартные) методы для работы с моделями. Все методы будут доступны через EntityManager для каждой модели, к которой будет «прикручен» конкретный репозиторий.

Сами репозитории являются наследниками класса EntityRepository. Их лучше хранить в отдельной папке (например Repositories) и в своем пространстве имен (namespace) — пусть неймспейс будет также Repositories.

Пример каркаса простого пользовательского репозитория:
namespace Repositories;

use Doctrine\ORM\EntityRepository;
use Entities;

class UserRepository extends EntityRepository {
    public function getUserMethod($params)    {    }
}



Структура проекта


Что бы было более понятно, определим структуру папок каталога нашего проекта:
  • /bin/ — скрипт для работы с доктриной через BASH
  • /doctrine/ — сама доктрина (установленная через GIT)
  • /Entities/ — наши модели с аннотациями (Созданные на основе YAML самой доктриной)
  • /Proxies/ — прокси для Доктрины
  • /public_html/ — пользовательская часть проекта
  • /Repositories/ — классы наших репозиториев (сюда мы их будем складывать)
  • /yaml/ — yaml мапперы наших моделей


Подключание к доктрине


Для начала, что бы доктрина начала видеть наши классы, мы в файле общей конфигурации Doctrine зарегистрируем еще один ClassLoader. В данном случае конфигурация лежит в файле cli-config.php. Добавляем в него следующие строчки кода:
// Константы для путей
define("ROOT_DIR", __DIR__);
define("DOCTRINE_DIR", ROOT_DIR."/doctrine");

...

// Если не заинклуден сам класслоадер, то инклудим сначала его
require_once DOCTRINE_DIR . '/lib/vendor/doctrine-common/lib/Doctrine/Common/ClassLoader.php';

// Теперь среди прочего регистрируем в класслоадере наши классы для репозитория
$classLoader = new ClassLoader('Repositories', ROOT_DIR);
$classLoader->register();

...


Подключение репозитория к модели


Место ассоциации модели с репозиторием зависит от того, какой тип мапперов мы используем (Аннтоации, YAML или XML). Поскольку у нас используется YAML, то подключаем репозиторий через него, используя атрибут repositoryClass где мы описываем название класса и его пространство имен:
Entities\ModelName:
  type: entity
  repositoryClass: Repositories\UserRepository
  table: model_table
  fields:
    ...


В данном примере подразумевается, что у нас есть сгенерированная модель с аннтоциями для данного YAML.
namespace Entities;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\EntityRepository;

/**
 * Entities\ModelName
 *
 * @Table(name="model_table")
 * @Entity(repositoryClass="Repositories\UserRepository")
 */
class ModelName
{
    ...
}


Вот собственно и все. теперь можно писать собственные методы в репозитории и работать с ними через наши модели. Все оказалось просто.

Пример


Небольшой жизненный пример, основанный на написании собственного метода к модели пользовательского настроение.
/Entities/UserMood.php
namespace Entities;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\EntityRepository;

/**
 * Entities\UserMood
 *
 * @Table(name="b_mood")
 * @Entity(repositoryClass="Repositories\RUserMood")
 */
class UserMood
{
    /**
     * @var integer $ID
     *
     * @Column(name="ID", type="integer")
     * @Id
     * @GeneratedValue(strategy="IDENTITY")
     */
    private $ID;

    /**
     * @var string $ACTIVE
     *
     * @Column(name="ACTIVE", type="string", length=1, nullable=false)
     */
    private $ACTIVE;

    /**
     * @var string $NAME
     *
     * @Column(name="NAME", type="string", length=255, nullable=false)
     */
    private $NAME;

    /**
     * @var datetime $DATE_ACTIVE_FROM
     *
     * @Column(name="DATE_ACTIVE_FROM", type="datetime", nullable=false)
     */
    private $DATE_ACTIVE_FROM;

    /**
     * @var decimal $VALUE
     *
     * @Column(name="VALUE", type="decimal", nullable=false)
     */
    private $VALUE;

    /**
     * @var integer $USER_ID
     *
     * @Column(name="USER_ID", type="integer", length=6, nullable=false)
     */
    private $USER_ID;

    /**
     * @var string $UNAUTH_HASH
     *
     * @Column(name="UNAUTH_HASH", type="string", length=255, nullable=false)
     */
    private $UNAUTH_HASH;

    
    //..... тут идут методы для работы с полями, которые я публиковать не буду для экномии места. 
    //..... методы созданы самой ORM.

}


/yaml/Entities.UserMood.dcm.yml

Entities\UserMood:
  type: entity
  repositoryClass: Repositories\RUserMood
  table: b_mood
  fields:
    ID:
      id: true
      type: integer
      generator:
        strategy: AUTO
    ACTIVE:
      type: string
      length: 1
      nullable: false
    NAME:
      type: string
      length: 255
      nullable: false
    DATE_ACTIVE_FROM:
      type: datetime
      nullable: false
    VALUE:
      type: decimal
      nullable: false
    USER_ID:
      type: integer
      length: 6
      nullable: false
    UNAUTH_HASH:
      type: string
      length: 255
      nullable: false


/Repositories/RUserMood.php

Тут стоит обратить внимание на то, как в нашем методе будет объявляется EntityManager ($em). Делается это так:

$this->_em


А теперь сам код нашего репозитория:

namespace Repositories;

use Doctrine\ORM\EntityRepository;
use Entities;

class RUserMood extends EntityRepository
{
    public function getMoodsInDateRange($from, $to, $user = false)
    {
        $qb = $this->_em->createQueryBuilder();
        
        $filter[1] = $from;
        $filter[2] = $to;
        
        if (!$user) {
           $varwhere = $qb->expr()->andX(
               $qb->expr()->gte('um.DATE_ACTIVE_FROM', '?1'),
               $qb->expr()->lte('um.DATE_ACTIVE_FROM', '?2')
           );
        } else {
           $filter[3] = $user;
           $varwhere = $qb->expr()->andX(
               $qb->expr()->gte('um.DATE_ACTIVE_FROM', '?1'),
               $qb->expr()->lte('um.DATE_ACTIVE_FROM', '?2'),
               $qb->expr()->eq('um.USER_ID', '?3')
           );
        }
        
        $qb->add('select', new \Doctrine\ORM\Query\Expr\Select(array('um')))
           ->add('from', new \Doctrine\ORM\Query\Expr\From('Entities\UserMood', 'um'))
           ->add('where', $varwhere)
           ->add('orderBy', new \Doctrine\ORM\Query\Expr\OrderBy('um.DATE_ACTIVE_FROM', 'DESC'))
           ->setParameters($filter);
           
        return $qb->getQuery();
    }
}


Вот и все! Теперь если мы обратимся к нашей модели то сможем работать с нашим методом! Например так:

include_once ('../cli-config.php');

$repo = DDB::getEM()->getRepository('Entities\UserMood')->getMoodsInDateRange('2011-05-01 00:00:00', '2011-05-09 23:59:59')->getResult();

print_r($repo);


Источники



P.S.

Поскольку я только начинаю активно использовать ORM на PHP и Doctrine в частности, то я не могу претендовать на 100% правильное решение данной задачи в данном материале, по этому здравая критика очень приветствуется! Всем спасибо!
Теги:
Хабы:
+13
Комментарии 7
Комментарии Комментарии 7

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн
PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн