В большинстве случаев стандартные методы, генерируемые доктриной на основе Yaml (XML или аннотаций), хватает только на получение каких то полей по какому-то простому фильтру. Для более сложного запроса приходиться пользоваться нативным QueryBuilder'ом и обращаться через dql запросы к нашей модели. Все это является следствием нагромождения больших кусков кода, которые имеют свойства дублироваться там где требуется применить идентичные запросы. А как хотелось бы обращаться с моделью просто и красиво через один единственный метод? Как? Напишем свой!
Нам помогут пользовательские репозитории! Если коротко, то смысл этих репозиториев в том, что бы хранить в себе пользовательские (нестандартные) методы для работы с моделями. Все методы будут доступны через EntityManager для каждой модели, к которой будет «прикручен» конкретный репозиторий.
Сами репозитории являются наследниками класса EntityRepository. Их лучше хранить в отдельной папке (например Repositories) и в своем пространстве имен (namespace) — пусть неймспейс будет также Repositories.
Пример каркаса простого пользовательского репозитория:
Что бы было более понятно, определим структуру папок каталога нашего проекта:
Для начала, что бы доктрина начала видеть наши классы, мы в файле общей конфигурации Doctrine зарегистрируем еще один ClassLoader. В данном случае конфигурация лежит в файле cli-config.php. Добавляем в него следующие строчки кода:
Место ассоциации модели с репозиторием зависит от того, какой тип мапперов мы используем (Аннтоации, YAML или XML). Поскольку у нас используется YAML, то подключаем репозиторий через него, используя атрибут repositoryClass где мы описываем название класса и его пространство имен:
В данном примере подразумевается, что у нас есть сгенерированная модель с аннтоциями для данного YAML.
Вот собственно и все. теперь можно писать собственные методы в репозитории и работать с ними через наши модели. Все оказалось просто.
Небольшой жизненный пример, основанный на написании собственного метода к модели пользовательского настроение.
/Entities/UserMood.php
/yaml/Entities.UserMood.dcm.yml
/Repositories/RUserMood.php
Тут стоит обратить внимание на то, как в нашем методе будет объявляется EntityManager ($em). Делается это так:
А теперь сам код нашего репозитория:
Вот и все! Теперь если мы обратимся к нашей модели то сможем работать с нашим методом! Например так:
Поскольку я только начинаю активно использовать ORM на PHP и Doctrine в частности, то я не могу претендовать на 100% правильное решение данной задачи в данном материале, по этому здравая критика очень приветствуется! Всем спасибо!
Пользовательские репозитории
Нам помогут пользовательские репозитории! Если коротко, то смысл этих репозиториев в том, что бы хранить в себе пользовательские (нестандартные) методы для работы с моделями. Все методы будут доступны через 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);
Источники
- Оригинальная документация по ORM Doctrine 2.1
- http://mackstar.com/blog/2010/10/04/using-repositories-doctrine-2
P.S.
Поскольку я только начинаю активно использовать ORM на PHP и Doctrine в частности, то я не могу претендовать на 100% правильное решение данной задачи в данном материале, по этому здравая критика очень приветствуется! Всем спасибо!