Pull to refresh

Symfony 2 и Doctrine 2

Reading time10 min
Views37K
Продолжая изучать Symfony 2 я решил описать использование фреймворка в связке с Doctrine 2, так как это один из самых часто задаваемых вопросов. И стоит заметить Doctrine 2 так же претерпел серьезные изменения в сравнении с веткой 1.x. Сам по себе проект Doctrine настолько большой, что описание его потянет пожалуй на небольшую, но интересную книгу. Поэтому я лишь очень бегло опишу пример использования Doctrine 2 в Symfony 2, позволяющий разобраться как запустить эту связку и сделаю это на примере очень меленького и простого приложения.

О 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
  1. <?php
  2.  
  3. /**<br/> * @Entity<br/> * @Table(name="my_model")<br/> */
  4. class MyModel
  5. {
  6.     /**<br/>     * @Id @Column(name="id", type="integer")<br/>     * @GeneratedValue(strategy="AUTO")<br/>     */
  7.     private $id;
  8.  
  9.     /**<br/>     * @Column(name="title", type="string", length=255)<br/>     */
  10.     private $title;
  11. }


Ну и конечно же обо всем этом и многом другом можно прочитать на сайте Doctrine

Взлетаем


Я решил рассмотреть использование Doctrine 2 на примере очень маленького и очень простого приложения. Это будет сервис Readlater, позволяющий сохранять ссылки с целью отложить чтение чего-либо на потом. То есть вся его функиональность сводится к трем простым действиям:
  • вывести список ссылок
  • добавить ссылку
  • удалить ссылку

Но я сразу же столкнулся с небольшой проблемой — и Doctrine 2 и Symfony 2 сейчас активно разрабатываются и ни о какой стабильноти и речи не идет. Более того все очень часто меняется и то что работает сегодня может не взлететь завтра. Зачастую это различные мелочи, вроде переноса кода в другую директорию. Поэтому для данного приложения я решил заморозить версии используемых библиотек. Для этого я взял:

Собрав это все воедино я заморозил все версии на 18 апреля 2010 года в том виде в котором они были и вместе со своим приложением выложил в общий доступ на github. Так что если интересно, то дерзайте.

Полетели


Итак, первое кратко о структуре. Примерно такой виде директорий:
  • readlater: — директория приложения
  • src
    • Application: — здесь будет лежать «бандл» ReadlaterBundle и в общем кодить будем там же.
    • Bundle: — в данном случае тут пусто
    • vendor: — здесь фреймворк и все библиотеки

Начнем с конфигов. В readlater/config/config.yml добавим:
Copy Source | Copy HTML
  1. kernel.config: ~
  2. web.web: ~
  3. web.templating: ~
  4. doctrine.dbal:
  5.   default_connection: default
  6.   connections:
  7.     default:
  8.       driver: PDOMySql
  9.       dbname: sfbox2
  10.       user: root
  11.       password: 123
  12.       host: localhost
  13.       event_manager_class: Doctrine\Common\EventManager
  14.       configuration_class: Doctrine\DBAL\Configuration
  15.  
  16. doctrine.orm:
  17.   default_entity_manager: default
  18.   cache_driver: array
  19.   entity_managers:
  20.     default:
  21.       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
  1. <?php
  2.  
  3. namespace Application\ReadlaterBundle\Entities;
  4. use Doctrine\ORM\EntityRepository;
  5.  
  6. /**<br/> * @Entity<br/> * @Table(name="readlater_link")<br/> */
  7. class Link
  8. {
  9.   /**<br/>   * @Id @Column(name="id", type="integer")<br/>   * @GeneratedValue(strategy="AUTO")<br/>   */
  10.   private $id;
  11.  
  12.   /**<br/>   * @Column(name="url", type="string", length=255)<br/>   */
  13.   private $url;
  14.  
  15.   /**<br/>   * @Column(name="created_at", type="datetime")<br/>   */
  16.   private $createdAt;
  17.  
  18.   /**<br/>   * constructor<br/>   */
  19.   public function __construct()
  20.   {
  21.     $this->createdAt = new \DateTime();
  22.   }
  23.  
  24.   /**<br/>   * to string<br/>   */
  25.   public function __toString()
  26.   {
  27.     return $this->getUrl();
  28.   }
  29. }
  30.  
  31.  

Я определил у класса три поля:
  • $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
  1. <?php
  2.  
  3. namespace Application\ReadlaterBundle\Entities;
  4. use Doctrine\ORM\EntityRepository;
  5.  
  6. /**<br/> * @Entity<br/> * @Table(name="readlater_link")<br/> */
  7. class Link
  8. {
  9.   /**<br/>   * @Id @Column(name="id", type="integer")<br/>   * @GeneratedValue(strategy="AUTO")<br/>   */
  10.   private $id;
  11.  
  12.   /**<br/>   * @Column(name="url", type="string", length=255)<br/>   */
  13.   private $url;
  14.  
  15.   /**<br/>   * @Column(name="created_at", type="datetime")<br/>   */
  16.   private $createdAt;
  17.  
  18.   /**<br/>   * constructor<br/>   */
  19.   public function __construct()
  20.   {
  21.     $this->createdAt = new \DateTime();
  22.   }
  23.  
  24.   /**<br/>   * to string<br/>   */
  25.   public function __toString()
  26.   {
  27.     return $this->getUrl();
  28.   }
  29.   /**<br/>   * Get id<br/>   *<br/>   * @return integer $id<br/>   */
  30.   public function getId()
  31.   {
  32.     return $this->id;
  33.   }
  34.  
  35.   /**<br/>   * Set url<br/>   *<br/>   * @param string $url<br/>   */
  36.   public function setUrl($url)
  37.   {
  38.     $this->url = $url;
  39.   }
  40.  
  41.   /**<br/>   * Get url<br/>   *<br/>   * @return string $url<br/>   */
  42.   public function getUrl()
  43.   {
  44.     return $this->url;
  45.   }
  46.  
  47.   /**<br/>   * Set createdAt<br/>   *<br/>   * @param datetime $createdAt<br/>   */
  48.   public function setCreatedAt($createdAt)
  49.   {
  50.     $this->createdAt = $createdAt;
  51.   }
  52.  
  53.   /**<br/>   * Get createdAt<br/>   *<br/>   * @return datetime $createdAt<br/>   */
  54.   public function getCreatedAt()
  55.   {
  56.     return $this->createdAt;
  57.   }
  58.  
  59. }
  60.  
  61.  

В нем появились методы для доступа к свойствам класса.

Что ж теперь добавим правила маршрутизации в routing.yml
Copy Source | Copy HTML
  1. link_list:
  2.   pattern: /
  3.   defaults: { _bundle: ReadlaterBundle, _controller: Readlater, _action: index }
  4.  
  5. add_link:
  6.   pattern: /add
  7.   defaults: { _bundle: ReadlaterBundle, _controller: Readlater, _action: add }
  8.  
  9. read_link:
  10.   pattern: /read/:id
  11.   defaults: { _bundle: ReadlaterBundle, _controller: Readlater, _action: read }

Из кода думаю все понятно, есть 3 правила, вывод списка, добавление и удаление.

Теперь наконец контроллер, в котором можно и посмотреть как работать с объектами под управлением Doctrine. Сразу привожу полностью весь код:
Copy Source | Copy HTML
  1. <?php
  2.  
  3. namespace Application\ReadlaterBundle\Controller;
  4.  
  5. use Symfony\Framework\WebBundle\Controller,
  6.   Application\ReadlaterBundle\Entities\Link,
  7.   Doctrine\ORM\QueryBuilder;
  8.  
  9. class ReadlaterController extends Controller
  10. {
  11.   public function indexAction()
  12.   {
  13.     $em = $this->container->getDoctrine_ORM_EntityManagerService();
  14.  
  15.     $links = $em->getRepository('Application\\ReadlaterBundle\Entities\Link')->findAll();
  16.     return $this->render('ReadlaterBundle:Readlater:index', array('links' => $links));
  17.   }
  18.  
  19.   public function addAction()
  20.   {
  21.     $request = $this->getRequest();
  22.  
  23.     $url = $request->request->get('link');
  24.     $link = new Link();
  25.     $link->setUrl($url);
  26.  
  27.     $em = $this->container->getDoctrine_ORM_EntityManagerService();
  28.     $em->persist($link);
  29.     $em->flush();
  30.  
  31.     return $this->redirect($this->generateUrl('link_list'));
  32.   }
  33.  
  34.   public function readAction($id)
  35.   {
  36.     $em = $this->container->getDoctrine_ORM_EntityManagerService();
  37.  
  38.     $link = $em->find('Application\\ReadlaterBundle\\Entities\\Link', $id);
  39.     $em->remove($link);
  40.  
  41.     $em->flush();
  42.  
  43.     return $this->redirect($this->generateUrl('link_list'));
  44.   }
  45. }

Пару слов о коде.
$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
  1. /**<br/> * LinkRepository<br/> */
  2. class LinkRepository extends EntityRepository
  3. {
  4.   public function findAll()
  5.   {
  6.     return $this->_em->createQuery('SELECT l FROM Application\\ReadlaterBundle\\Entities\\Link l ORDER BY l.createdAt DESC')->getResult();
  7.   }
  8. }

это и будет репозиторий.
Теперь скажем какой репозиторий использовать для класса Link, для этого немного изменим Docblock у класс Link
Copy Source | Copy HTML
  1. /**<br/> * @Entity(repositoryClass="Application\ReadlaterBundle\Entities\LinkRepository")<br/> * @Table(name="readlater_link")<br/> */
  2. class Link
  3. {
  4.   // ...
  5. }

Вот в общем и все. Теперь список ссылок будет сортироваться в нужном порядке и выглядеть это будет примерно так:


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

Articles