Продолжая изучать Symfony 2 я решил описать использование фреймворка в связке с Doctrine 2, так как это один из самых часто задаваемых вопросов. И стоит заметить Doctrine 2 так же претерпел серьезные изменения в сравнении с веткой 1.x. Сам по себе проект Doctrine настолько большой, что описание его потянет пожалуй на небольшую, но интересную книгу. Поэтому я лишь очень бегло опишу пример использования Doctrine 2 в Symfony 2, позволяющий разобраться как запустить эту связку и сделаю это на примере очень меленького и простого приложения.
Для начала небольшое вступление о Doctrine 2. Проект теперь разбит на три пакета:
Common — содержит общие компоненты, которые используются в других пакетах.
DBAL — слой абстракции доступа к БД.
ORM — инструменты объектно-реляционного отображения
В Doctrine 2 нет привычных классов *Table и *Record, которые были раньше. Это так же означает, что теперь классам моделей не надо наследовать классы *Record, а классам таблиц наследовать классы *Table из пакета Doctrine. Вместо этого вводятся два новых понятия «Entity» и «EntityManager». «Entity» — это, грубо говоря, объект модели, а «EntityManager» — это соответственно класс позволяющий управлять объектами «Entity». Интересно то, что «Entity» может быть любой PHP класс и он не должен наследовать никаких классов из пакета Doctrine.
В Doctrine 1 модели описывались в Yaml файле, теперь же для этого можно использовать Yaml, XML или Docblock Annotations. Docblock annotations в том или ином виде приходилось наверное использовать всем, например для описания методов под PHPDocumentor. Так вот теперь так же можно описывать метаданные persistent объектов.
Пример:
Ну и конечно же обо всем этом и многом другом можно прочитать на сайте Doctrine
Я решил рассмотреть использование Doctrine 2 на примере очень маленького и очень простого приложения. Это будет сервис Readlater, позволяющий сохранять ссылки с целью отложить чтение чего-либо на потом. То есть вся его функиональность сводится к трем простым действиям:
Но я сразу же столкнулся с небольшой проблемой — и Doctrine 2 и Symfony 2 сейчас активно разрабатываются и ни о какой стабильноти и речи не идет. Более того все очень часто меняется и то что работает сегодня может не взлететь завтра. Зачастую это различные мелочи, вроде переноса кода в другую директорию. Поэтому для данного приложения я решил заморозить версии используемых библиотек. Для этого я взял:
Собрав это все воедино я заморозил все версии на 18 апреля 2010 года в том виде в котором они были и вместе со своим приложением выложил в общий доступ на github. Так что если интересно, то дерзайте.
Итак, первое кратко о структуре. Примерно такой виде директорий:
Начнем с конфигов. В readlater/config/config.yml добавим:
Я описал соединение с базой данных doctrine.dbal и настройки для пакета Doctrine\ORM doctrine.orm
Я не буду здесь описывать создание ядра приложения, так как оно практически не отличается от того что идет в symfony-sandbox.
Поэтому я перейду к ReadlaterBundle, который помещу в src/Application. Структура бандла такова:
В директории Entities я создам единственный класс Link, потому что больше и не надо. Doctrine 2 по умолчанию предлагает создавать Entity в директории Entities бандла, то есть каждый бандл может иметь свой набор классов моделей. Путем настроек можно попросить Doctrine искать классы в отличной от Entities директории, но я этого делать не буду.
Итак приведу код класса Link:
Я определил у класса три поля:
А так же добавил два метода, которые и так понятны. Как видно из примера для описания модели я воспользовался Docblock.
Теперь выполнил несколько команд:
первая команда создаст базу данных, вторая сгенерирует методы для нашего класса и произведет кое-какую работу и третья создаст таблицу в базе данных.
выглядеть это будет примерно так:

Теперь если заглянем в наш класс Link то увидим, что он немного преобразился, а имеено теперь он выглядит так:
В нем появились методы для доступа к свойствам класса.
Что ж теперь добавим правила маршрутизации в routing.yml
Из кода думаю все понятно, есть 3 правила, вывод списка, добавление и удаление.
Теперь наконец контроллер, в котором можно и посмотреть как работать с объектами под управлением Doctrine. Сразу привожу полностью весь код:
Пару слов о коде.
Кстати если бы я унаследовал свой контроллер от Symfony\Framework\DoctrineBundle\Controller\DoctrineController, то были бы доступны методы:
Вот собственно и все о коде, остальное думаю очевидно.
Но возможно возник вопрос, если теперь нет классов таблиц, то где создавать методы извлечения модельных объектов. Например в данном случае хорошо бы чтоб метод findAll получа�� список объектов отсортированных по дате добавления по убыванию. Для этого необходимо создать свой класс репозиторий хранения для объектов определенного типа.
Для этого откроем наш класс Link и в конец файла допишем:
это и будет репозиторий.
Теперь скажем какой репозиторий использовать для класса Link, для этого немного изменим Docblock у класс Link
Вот в общем и все. Теперь список ссылок будет сортироваться в нужном порядке и выглядеть это будет примерно так:

Код шаблонов я не стал приводить, но все это вместе можно посмотреть в исходниках на github
а так же ссылки по теме:
О Doctrine 2
Для начала небольшое вступление о Doctrine 2. Проект теперь разбит на три пакета:
- Common
- DBAL
- ORM
Common — содержит общие компоненты, которые используются в других пакетах.
DBAL — слой абстракции доступа к БД.
ORM — инструменты объектно-реляционного отображения
В Doctrine 2 нет привычных классов *Table и *Record, которые были раньше. Это так же означает, что теперь классам моделей не надо наследовать классы *Record, а классам таблиц наследовать классы *Table из пакета Doctrine. Вместо этого вводятся два новых понятия «Entity» и «EntityManager». «Entity» — это, грубо говоря, объект модели, а «EntityManager» — это соответственно класс позволяющий управлять объектами «Entity». Интересно то, что «Entity» может быть любой PHP класс и он не должен наследовать никаких классов из пакета Doctrine.
В Doctrine 1 модели описывались в Yaml файле, теперь же для этого можно использовать Yaml, XML или Docblock Annotations. Docblock annotations в том или ином виде приходилось наверное использовать всем, например для описания методов под PHPDocumentor. Так вот теперь так же можно описывать метаданные persistent объектов.
Пример:
Copy Source | Copy HTML
- <?php
-
- /**<br/> * @Entity<br/> * @Table(name="my_model")<br/> */
- class MyModel
- {
- /**<br/> * @Id @Column(name="id", type="integer")<br/> * @GeneratedValue(strategy="AUTO")<br/> */
- private $id;
-
- /**<br/> * @Column(name="title", type="string", length=255)<br/> */
- private $title;
- }
Ну и конечно же обо всем этом и многом другом можно прочитать на сайте Doctrine
Взлетаем
Я решил рассмотреть использование Doctrine 2 на примере очень маленького и очень простого приложения. Это будет сервис Readlater, позволяющий сохранять ссылки с целью отложить чтение чего-либо на потом. То есть вся его функиональность сводится к трем простым действиям:
- вывести список ссылок
- добавить ссылку
- удалить ссылку
Но я сразу же столкнулся с небольшой проблемой — и Doctrine 2 и Symfony 2 сейчас активно разрабатываются и ни о какой стабильноти и речи не идет. Более того все очень часто меняется и то что работает сегодня может не взлететь завтра. Зачастую это различные мелочи, вроде переноса кода в другую директорию. Поэтому для данного приложения я решил заморозить версии используемых библиотек. Для этого я взял:
- symfony-sandbox: sandbox на его основе все и строилось
- symfony: форк symfony от Jonathan Wage разработчика Doctrine
- doctrine migrations: миграции
- doctrine: trunk doctrine
Собрав это все воедино я заморозил все версии на 18 апреля 2010 года в том виде в котором они были и вместе со своим приложением выложил в общий доступ на github. Так что если интересно, то дерзайте.
Полетели
Итак, первое кратко о структуре. Примерно такой виде директорий:
- readlater: — директория приложения
- src
- Application: — здесь будет лежать «бандл» ReadlaterBundle и в общем кодить будем там же.
- Bundle: — в данном случае тут пусто
- vendor: — здесь фреймворк и все библиотеки
Начнем с конфигов. В readlater/config/config.yml добавим:
Copy Source | Copy HTML
- kernel.config: ~
- web.web: ~
- web.templating: ~
- doctrine.dbal:
- default_connection: default
- connections:
- default:
- driver: PDOMySql
- dbname: sfbox2
- user: root
- password: 123
- host: localhost
- event_manager_class: Doctrine\Common\EventManager
- configuration_class: Doctrine\DBAL\Configuration
-
- doctrine.orm:
- default_entity_manager: default
- cache_driver: array
- entity_managers:
- default:
- connection: default
Я описал соединение с базой данных doctrine.dbal и настройки для пакета Doctrine\ORM doctrine.orm
Я не буду здесь описывать создание ядра приложения, так как оно практически не отличается от того что идет в symfony-sandbox.
Поэтому я перейду к ReadlaterBundle, который помещу в src/Application. Структура бандла такова:
- Controller: — здесь контроллеры
- Entities: — здесь те самые Entity, классы моделей
- Resources
- config: — здесь настройки
- views: — здесь шаблоны
В директории Entities я создам единственный класс Link, потому что больше и не надо. Doctrine 2 по умолчанию предлагает создавать Entity в директории Entities бандла, то есть каждый бандл может иметь свой набор классов моделей. Путем настроек можно попросить Doctrine искать классы в отличной от Entities директории, но я этого делать не буду.
Итак приведу код класса Link:
Copy Source | Copy HTML
- <?php
-
- namespace Application\ReadlaterBundle\Entities;
- use Doctrine\ORM\EntityRepository;
-
- /**<br/> * @Entity<br/> * @Table(name="readlater_link")<br/> */
- class Link
- {
- /**<br/> * @Id @Column(name="id", type="integer")<br/> * @GeneratedValue(strategy="AUTO")<br/> */
- private $id;
-
- /**<br/> * @Column(name="url", type="string", length=255)<br/> */
- private $url;
-
- /**<br/> * @Column(name="created_at", type="datetime")<br/> */
- private $createdAt;
-
- /**<br/> * constructor<br/> */
- public function __construct()
- {
- $this->createdAt = new \DateTime();
- }
-
- /**<br/> * to string<br/> */
- public function __toString()
- {
- return $this->getUrl();
- }
- }
-
-
Я определил у класса три поля:
- $id — индентификатор
- $url — URL ссылки
- $createdAt — дата добавления
А так же добавил два метода, которые и так понятны. Как видно из примера для описания модели я воспользовался Docblock.
Теперь выполнил несколько команд:
readlater/console doctrine:database:create
readlater/console doctrine:generate:entities
readlater/console doctrine:schema:create
первая команда создаст базу данных, вторая сгенерирует методы для нашего класса и произведет кое-какую работу и третья создаст таблицу в базе данных.
выглядеть это будет примерно так:

Теперь если заглянем в наш класс Link то увидим, что он немного преобразился, а имеено теперь он выглядит так:
Copy Source | Copy HTML
- <?php
-
- namespace Application\ReadlaterBundle\Entities;
- use Doctrine\ORM\EntityRepository;
-
- /**<br/> * @Entity<br/> * @Table(name="readlater_link")<br/> */
- class Link
- {
- /**<br/> * @Id @Column(name="id", type="integer")<br/> * @GeneratedValue(strategy="AUTO")<br/> */
- private $id;
-
- /**<br/> * @Column(name="url", type="string", length=255)<br/> */
- private $url;
-
- /**<br/> * @Column(name="created_at", type="datetime")<br/> */
- private $createdAt;
-
- /**<br/> * constructor<br/> */
- public function __construct()
- {
- $this->createdAt = new \DateTime();
- }
-
- /**<br/> * to string<br/> */
- public function __toString()
- {
- return $this->getUrl();
- }
- /**<br/> * Get id<br/> *<br/> * @return integer $id<br/> */
- public function getId()
- {
- return $this->id;
- }
-
- /**<br/> * Set url<br/> *<br/> * @param string $url<br/> */
- public function setUrl($url)
- {
- $this->url = $url;
- }
-
- /**<br/> * Get url<br/> *<br/> * @return string $url<br/> */
- public function getUrl()
- {
- return $this->url;
- }
-
- /**<br/> * Set createdAt<br/> *<br/> * @param datetime $createdAt<br/> */
- public function setCreatedAt($createdAt)
- {
- $this->createdAt = $createdAt;
- }
-
- /**<br/> * Get createdAt<br/> *<br/> * @return datetime $createdAt<br/> */
- public function getCreatedAt()
- {
- return $this->createdAt;
- }
-
- }
-
-
В нем появились методы для доступа к свойствам класса.
Что ж теперь добавим правила маршрутизации в routing.yml
Copy Source | Copy HTML
- link_list:
- pattern: /
- defaults: { _bundle: ReadlaterBundle, _controller: Readlater, _action: index }
-
- add_link:
- pattern: /add
- defaults: { _bundle: ReadlaterBundle, _controller: Readlater, _action: add }
-
- read_link:
- pattern: /read/:id
- defaults: { _bundle: ReadlaterBundle, _controller: Readlater, _action: read }
Из кода думаю все понятно, есть 3 правила, вывод списка, добавление и удаление.
Теперь наконец контроллер, в котором можно и посмотреть как работать с объектами под управлением Doctrine. Сразу привожу полностью весь код:
Copy Source | Copy HTML
- <?php
-
- namespace Application\ReadlaterBundle\Controller;
-
- use Symfony\Framework\WebBundle\Controller,
- Application\ReadlaterBundle\Entities\Link,
- Doctrine\ORM\QueryBuilder;
-
- class ReadlaterController extends Controller
- {
- public function indexAction()
- {
- $em = $this->container->getDoctrine_ORM_EntityManagerService();
-
- $links = $em->getRepository('Application\\ReadlaterBundle\Entities\Link')->findAll();
- return $this->render('ReadlaterBundle:Readlater:index', array('links' => $links));
- }
-
- public function addAction()
- {
- $request = $this->getRequest();
-
- $url = $request->request->get('link');
- $link = new Link();
- $link->setUrl($url);
-
- $em = $this->container->getDoctrine_ORM_EntityManagerService();
- $em->persist($link);
- $em->flush();
-
- return $this->redirect($this->generateUrl('link_list'));
- }
-
- public function readAction($id)
- {
- $em = $this->container->getDoctrine_ORM_EntityManagerService();
-
- $link = $em->find('Application\\ReadlaterBundle\\Entities\\Link', $id);
- $em->remove($link);
-
- $em->flush();
-
- return $this->redirect($this->generateUrl('link_list'));
- }
- }
Пару слов о коде.
$em = $this->container->getDoctrine_ORM_EntityManagerService(); таким образом используя DI Container мы получаем доступ к EntityManager$em->getRepository() — так мы получаем хранилище объектов определенного типа, то есть некий аналог *Table из Doctrine 1.x.$em->persist($link); — так сохраняем объект$em->flush() так завершаем транзакцию, то есть реально отправляем данные в БД. $em->remove($link) — ну а так удалем объект.Кстати если бы я унаследовал свой контроллер от Symfony\Framework\DoctrineBundle\Controller\DoctrineController, то были бы доступны методы:
- getDatabaseConnection — возвращает объект соединения с БД
- getEntityManager — и мне бы не пришлось его получать из DI Container'a
- createQueryBuilder
- createQuery
Вот собственно и все о коде, остальное думаю очевидно.
Но возможно возник вопрос, если теперь нет классов таблиц, то где создавать методы извлечения модельных объектов. Например в данном случае хорошо бы чтоб метод findAll получа�� список объектов отсортированных по дате добавления по убыванию. Для этого необходимо создать свой класс репозиторий хранения для объектов определенного типа.
Для этого откроем наш класс Link и в конец файла допишем:
Copy Source | Copy HTML
- /**<br/> * LinkRepository<br/> */
- class LinkRepository extends EntityRepository
- {
- public function findAll()
- {
- return $this->_em->createQuery('SELECT l FROM Application\\ReadlaterBundle\\Entities\\Link l ORDER BY l.createdAt DESC')->getResult();
- }
- }
это и будет репозиторий.
Теперь скажем какой репозиторий использовать для класса Link, для этого немного изменим Docblock у класс Link
Copy Source | Copy HTML
- /**<br/> * @Entity(repositoryClass="Application\ReadlaterBundle\Entities\LinkRepository")<br/> * @Table(name="readlater_link")<br/> */
- class Link
- {
- // ...
- }
Вот в общем и все. Теперь список ссылок будет сортироваться в нужном порядке и выглядеть это будет примерно так:

Код шаблонов я не стал приводить, но все это вместе можно посмотреть в исходниках на github
а так же ссылки по теме: