Базы данных и Doctrine ORM

Базы данных и Doctrine ORM

Одни из самых распространённых и сложных задач для любого приложения – это хранение и считывание информации из базы данных. Несмотря на то, что фреймворк Symfony не включает компонент для работы с базами данных, он предоставляет тесную интеграцию со сторонней библиотекой Doctrine, единственная цель которой – предоставить вам мощные инструменты, для простого и гибкого взаимодействия с базами данных.

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

Note

Doctrine полностью отделена от Symfony, и её использование необязательно. Эта глава полностью посвящена Doctrine ORM, цель которой – позволить вам отобразить (map) объекты в реляционной базе данных (такой как MySQL, PostgreSQL или Microsoft SQL). Если вы предпочитаете использовать прямые запросы к БД, это тоже несложно, и раскрыто в статье "Как использовать Doctrine DBAL".

Также можно хранить данные в MongoDB используя библиотеку Doctrine ODM. Для дополнительной информации, прочтите документацию "DoctrineMongoDBBundle".

Простой пример: Товар (Product)

Проще всего понять, как работает Doctrine - это увидеть её в действии. В этом разделе вы настроите вашу базу данных, создадите объект Product, поместите его в БД и извлечете обратно.

Конфигурация базы данных

Перед тем, как действительно начать, вам необходимо настроить соединение с базой данных. По соглашению, эта информация обычно указывается в файле app/config/parameters.yml:

1
2
3
4
5
6
7
8
# app/config/parameters.yml
parameters:
    database_host:     localhost
    database_name:     test_project
    database_user:     root
    database_password: password

# ...

Note

Указание параметров в parameters.yml – это всего лишь соглашение. На них ссылается основной файл конфигурации, во время настройки Doctrine:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    # app/config/config.yml
    doctrine:
        dbal:
            driver:   pdo_mysql
            host:     '%database_host%'
            dbname:   '%database_name%'
            user:     '%database_user%'
            password: '%database_password%'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!-- app/config/config.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:doctrine="http://symfony.com/schema/dic/doctrine"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/doctrine
            http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
    
        <doctrine:config>
            <doctrine:dbal
                driver="pdo_mysql"
                host="%database_host%"
                dbname="%database_name%"
                user="%database_user%"
                password="%database_password%" />
        </doctrine:config>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // app/config/config.php
    $container->loadFromExtension('doctrine', array(
        'dbal' => array(
            'driver'   => 'pdo_mysql',
            'host'     => '%database_host%',
            'dbname'   => '%database_name%',
            'user'     => '%database_user%',
            'password' => '%database_password%',
        ),
    ));
    

Выделяя информацию о базе данных в отдельный файл, вы с легкостью сможете хранить различные версии этих файлов на каждом сервере. Вы также легко сможете хранить конфигурацию базы данных (или любую важную информацию) вне вашего проекта, например, в конфигурационных файлах Apache. Дополнительную информацию смотрите в разделе Как устанавливать внешние параметры в контейнере служб.

Теперь, когда Doctrine может подключиться к базе данных, следующая команда может автоматически сгенерировать для вас пустую базу данных test_project:

1
$ php bin/console doctrine:database:create

Частая ошибка, которую делают даже опытные разработчики при создании проекта Symfony, - они забывают установить в своей базе данных кодировку (charset) и сопоставление (collation) по умолчанию, что приводит к использованию сопоставления на латинице, что является стандартным для большинства БД. Возможно, они даже не забудут сделать это в самый первый раз, но забудут о том, что все исчезнет после запуска относительно частой команды во время разработки:

1
2
$ php bin/console doctrine:database:drop --force
$ php bin/console doctrine:database:create

Установка UTF8 по умолчаию для MySQL – это так же легко, как добавить несколько строк в ваш файл конфигурации (обычно my.cnf):

1
2
3
4
[mysqld]
# В версии 5.5.3 добавили кодировку "utf8mb4", которую рекомендуется использовать
collation-server     = utf8mb4_unicode_ci # Вместо utf8_unicode_ci
character-set-server = utf8mb4            # Вместо utf8

Вы также можете изменить параметры по умолчанию в Doctrine, чтобы созданный SQL использовал верный набор символов.

  • YAML
    1
    2
    3
    4
    5
    6
    7
    # app/config/config.yml
    doctrine:
        dbal:
            charset: utf8mb4
            default_table_options:
                charset: utf8mb4
                collate: utf8mb4_unicode_ci
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!-- app/config/config.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:doctrine="http://symfony.com/schema/dic/doctrine"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/doctrine
            http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
    
        <doctrine:config>
            <doctrine:dbal
                charset="utf8mb4">
                    <doctrine:default-table-option name="charset">utf8mb4</doctrine:default-table-option>
                    <doctrine:default-table-option name="collate">utf8mb4_unicode_ci</doctrine:default-table-option>
            </doctrine:dbal>
        </doctrine:config>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // app/config/config.php
    $configuration->loadFromExtension('doctrine', array(
        'dbal' => array(
            'charset' => 'utf8mb4',
            'default_table_options' => array(
                'charset' => 'utf8mb4'
                'collate' => 'utf8mb4_unicode_ci'
            )
        ),
    ));
    

Мы не советуем использовать набор символов utf8 MySQL, так как он не поддерживает 4-битные символы уникода, и содержащие их строки будут усечены. Это можно исправить с помощью более новой кодировки utf8mb4.

Note

Если вы хотите использовать SQLite в качестве своей базы данных, то вам нужно установить путь к месту хранения файла вашей БД:

  • YAML
    1
    2
    3
    4
    5
    6
    # app/config/config.yml
    doctrine:
        dbal:
            driver: pdo_sqlite
            path: '%kernel.project_dir%/app/sqlite.db'
            charset: UTF8
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- app/config/config.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:doctrine="http://symfony.com/schema/dic/doctrine"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/doctrine
            http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
    
        <doctrine:config>
            <doctrine:dbal
                driver="pdo_sqlite"
                path="%kernel.project_dir%/app/sqlite.db"
                charset="UTF-8" />
        </doctrine:config>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    // app/config/config.php
    $container->loadFromExtension('doctrine', array(
        'dbal' => array(
            'driver'  => 'pdo_sqlite',
            'path'    => '%kernel.project_dir%/app/sqlite.db',
            'charset' => 'UTF-8',
        ),
    ));
    

Создание класса сущности (Entity)

Предположим, что вы создаёте приложение, в котором необходимо отображать товары. Даже не задумываясь о Doctrine или базах данных, вы уже знаете, что вам необходим объект Product, чтобы представить эти товары. Создайте этот класс внутри каталога Entity в AppBundle:

1
2
3
4
5
6
7
8
9
// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;

class Product
{
    private $name;
    private $price;
    private $description;
}

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

Tip

Когда вы изучите понятия Doctrine, то сможете поручить ей создать этот простой класс-сущность за вас. Это будет задавать вам интерактивные вопросы, чтобы помочь вам построить сущность:

1
$ php bin/console doctrine:generate:entity

Добавляем информацию об отображении (mapping)

Doctrine позволяет вам работать с базами данных гораздо более интересным способом, чем простое получение строк скалярных данных в массив. Вместо этого, Doctrine позволяет вам получить сразу объекты из базе данных и сохранять объекты в базу данных. Чтобы Doctrine могла делать это, вы должны отобразить (map) ваши таблицы из базы данных в определённые PHP-классы и столбцы этих таблиц должны быть отображены в определённые свойства соответствующих PHP-классов.

_images/mapping_single_entity.png

Вы предоставите эту информацию об отображении в форме «метаданных» - наборе правил, которые которые в точности расскажут Doctrine, как класс Product и его свойства должны быть отображены (mapped) в таблице в базе данных. Эти метаданные могут быть указаны в большом количестве форматов, включая YAML, XML или прямо внутри класса Product через аннотации DocBlock:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // src/AppBundle/Entity/Product.php
    namespace AppBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * @ORM\Entity
     * @ORM\Table(name="product")
     */
    class Product
    {
        /**
         * @ORM\Column(type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        private $id;
    
        /**
         * @ORM\Column(type="string", length=100)
         */
        private $name;
    
        /**
         * @ORM\Column(type="decimal", scale=2)
         */
        private $price;
    
        /**
         * @ORM\Column(type="text")
         */
        private $description;
    }
    
  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    # src/AppBundle/Resources/config/doctrine/Product.orm.yml
    AppBundle\Entity\Product:
        type: entity
        table: product
        id:
            id:
                type: integer
                generator: { strategy: AUTO }
        fields:
            name:
                type: string
                length: 100
            price:
                type: decimal
                scale: 2
            description:
                type: text
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <!-- src/AppBundle/Resources/config/doctrine/Product.orm.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
            http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
    
        <entity name="AppBundle\Entity\Product" table="product">
            <id name="id" type="integer">
                <generator strategy="AUTO" />
            </id>
            <field name="name" type="string" length="100" />
            <field name="price" type="decimal" scale="2" />
            <field name="description" type="text" />
        </entity>
    </doctrine-mapping>
    

Note

Пакет (Bundle) может использовать только один формат определения метаданных. Например, нельзя смешивать YAML определения метаданных и определения через аннотациии в классе-сущности PHP.

Tip

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

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

Вы также можете просмотреть документацию Базовое отображение Doctrine для того, чтобы узнать детали об отображении информации. Если вы будете использовать аннотации, вам нужно будет вставить перед всеми из них ORM\ (например, ORM\Column(...)), что не указано в документации Doctrine. Вам также понадобится добавить выражение use Doctrine\ORM\Mapping as ORM;, которое импортирует префикс аннотаций ORM.

Caution

Будьте осторожны, если имена ваших сущностных классов (или их свойства) также являются зарезервированными ключевыми словами SQL (такие как GROUP или USER). Например, если имя вашего сущностного класса – Group, тогда, по умолчанию, имя соответствующей таблицы также будет group. Это приведет к ошибке SQL в некоторых движках баз данных. Смотрите документацию Зарезервированные ключевые слова SQL, чтобы узнать, как лучше избегать таких имен. В качестве альтернативы, если вы вольны выбирать схему вашей базы данных, просто используйте в отображении другое имя таблицы или название столбца. Смотрите документацию Doctrine Создание классов для баз данных и Отображение свойств.

Note

Когда используется другая библиотека или программа (например, Doxygen), использующая аннотации, необходимо поместить в класс аннотацию @IgnoreAnnotation , чтобы указать, какие из них Symfony должен игнорировать.

Например, чтобы сделать, что аннотациа @fn не будет выбрасывать исключение, добавьте следующее:

1
2
3
4
5
/**
 * @IgnoreAnnotation("fn")
 */
class Product
// ...

Tip

После создания ваших сущностей, вам нужно валидировать отображения с помощью следующей команды:

1
$ php bin/console doctrine:schema:validate

Создание геттеров и сеттеров

Несмотря на то, что теперь Doctrine знает, как сохранить объект Product в базу данных, сам класс пока ещё бесполезен. Так как Product всего лишь обычный PHP-класс с private (приватными) свойствами, вам необходимо создать public (публичные) методы геттеры и сеттеры (например, getName(), setName($name)), чтобы получить доступ к его свойствам в остальном коде вашего приложения. К счастью, следующая команда может создать эти шаблонные методы автоматически:

1
$ php bin/console doctrine:generate:entities AppBundle/Entity/Product

Эта команда удостоверяется, что все геттеры и сеттеры созданы для класса Product. Это безопасная команда – вы можете запускать её снова и снова: она создаёт лишь геттеры и сеттеры, которых ещё нет (то есть, она не изменит уже существующие методы).

Caution

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

C помощью команды doctrine:generate:entities вы можете:

  • генерировать методы геттеры и сеттеры в классах-сущностях;
  • генерировать классы репозитории для сушностей, настроенных с помощью аннотации @ORM\Entity(repositoryClass="...");
  • генерировать соответствующий конструктор для отношений 1:n (один ко многим, многие к одному) и n:m (многие ко многим).

Команда doctrine:generate:entities сохраняет резервную копию оригинала Product.php под именем Product.php~. В некоторых случаях, присутствие этого файла может вызвать ошибку «Cannot redeclare class» (Невозможно повторно объявить класс». Его можно безопасно удалить. Вы также можете использовать опцию --no-backup, чтобы предотвратить создание этих резервных копий.

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

Вы также можете создать все известные сущности (например, любой PHP класс с информацией для отображения Doctrine) для пакета или целого пространства имён:

1
2
3
4
5
# генерирует все сущности в AppBundle
$ php bin/console doctrine:generate:entities AppBundle

# генерирует все сущности пакетов в пространстве имен Acme
$ php bin/console doctrine:generate:entities Acme

Создание таблиц/схемы для базы данных

Теперь у вас есть полезный класс Product с информацией для отображения, благодаря которой Doctrine точно знает как его сохранить. Конечно, у вас пока нет соотвествующей таблицы product в вашей базе данных. К счастью, Doctrine может автоматически создать все таблицы базы данных, необходимые для всех известных сущностей вашего приложения. Чтобы создать их, выполните:

1
$ php bin/console doctrine:schema:update --force

Tip

Эта команда невероятно мощная. Она сравнивает то, как должна выглядеть ваша база данных (основываясь на информации об отображении ваших сущностей) с тем, как она выглядит на самом деле, и выполняет SQL выражения, необходимые для обновления схемы базы данных до того вида, какой она должна быть. Другими словами, добавив новое свойство с метаданными отображения в Product и запустив её снова, она создаст выражение "ALTER TABLE", необходимое для добавления этого нового столбца к существующей таблице product .

Еще лучший способ использовать преимущества этой функциональности – это с помощью миграций (migrations), которые позволяют создавать эти SQL выражения и хранить их в миграционных классах. Они, в свою очередь, могут систематически запускаться на ващем продакшн сервере, чтобы обновить и отследить изменения в схеме вашей базы данных безопасно и надёжно.

Будете ли вы использовать преимущества миграций, или нет, команда doctrine:schema:update должна быть использована только во время разработки. Её не стоит использовать на работающем проекте в интернете.

Теперь ваша база данных имеет полноценно функционирующую таблицу product со столбцами, соответствующими указанным вами метаданным.

Сохранение объектов в базе данных

Теперь, когда вы отобразили сущность Product в соотвествующей таблице product, вы готовы к сохранению объектов Product в БД. Внутри контроллера это достаточно просто. Добавьте следующий метод в DefaultController пакета:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// src/AppBundle/Controller/DefaultController.php

// ...
use AppBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Common\Persistence\ManagerRegistry;

public function createAction(EntityManagerInterface $em)
{
    // или получите менеджер сущностей (EntityManager) из контейнера
    // $em = $this->get('doctrine')->getManager();

    $product = new Product();
    $product->setName('Клавиатура');
    $product->setPrice(19.99);
    $product->setDescription('Модная и удобная!');

    // сообщает Doctrine, что вы (в итоге) хотите сохранить Product (пока запросов не будет)
    $em->persist($product);

    // теперь выполняются запросы (например, запрос INSERT)
    $em->flush();

    return new Response('Сохранили новый продукт с id '.$product->getId());
}

// если у вас есть несколько менеджеров сущностей, испольуйте реестр, чтобы получить их
public function editAction(ManagerRegistry $doctrine)
{
    $em = $doctrine->getManager();
    $em2 = $doctrine->getManager('other_connection')
}

Note

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

Рассмотрите предыдущий пример более детально:

  • Строка 10 Подсказка типа EntityManagerInterface говорит Symfony передать вам объект Doctrine менеджер сущностей (entity manager), который является самым важным объектом Doctrine. Он отвечает за сохранение в базу данных, и получение объектов из базы данных.
  • Строки 15-18 В этой части вы создаёте объект $product и работаете с ним, как и с любым другим обычным PHP-объектом.
  • Строка 21 Вызов persist($product) сообщает Doctrine, чтобы он "управлял" объектом $product. Это не создает запрос в базу данных.
  • Строка 24 Когда вызывается метод flush(), Doctrine просматривает все объекты, которыми он управляет, чтобы узнать, нужно ли их сохранять в базу данных. В этом примере, объект $product не существует в базе данных, так что менеджер сущностей выполняет запрос INSERT, создавая новую строку в таблице product.

Note

Фактически, так как Doctrine знает обо всех ваших управляемых сущностях, когда вызывается метод flush(), она просчитывает общий набор изменений и выполняет запросы в правильном порядке. Она использует подготовленное кэшированное выражение, для небольшого улучшения производительности. Например, если сохраняется 100 объектов Product и впоследствии вызывается flush(), то Doctrine выполнит 100 запросов INSERT, используя объект единственного подготовленного выражения.

Note

Если вызов flush() не удается, то вызывается исключение Doctrine\ORM\ORMException. См. Транзакции и параллелизм (Concurrency).

Процесс одинаков и для создания, и для обновления объектов. В следующем разделе вы увидите, что Doctrine достаточно умна для того, чтобы автоматически выдать запрос UPDATE, если запись уже существует в базе данных.

Tip

Doctrine предлагает библиотеку, позволяющую вам программно загружать тестовые данные в ваш проект, то есть "fixture data" (предустановленные данные). Для информации, смотрите документацию "DoctrineFixturesBundle".

Получение объектов из базы данных

Получение объекта назад из базы данных ещё проще. Например, представим что вы создали маршрут, отображающий определённый Product, основываясь на его значении id:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use Doctrine\ORM\EntityManagerInterface;

public function showAction($productId, EntityManagerInterface $em)
{
    $product = $em->getRepository('AppBundle:Product')
        ->find($productId);

    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$productId
        );
    }

    // ... сделать что-либо, например, передать объект $product в шаблон
}

Tip

Вы можете достичь эквивалент этого без написания кода, используя сокращение @ParamConverter. Смотрите документацию FrameworkExtraBundle , чтобы узнать подробнее.

Когда вы запрашиваете объект определённого типа, вы всегда используете его так называемый "репозиторий". Можно представить репозиторий как PHP-класс, чья единственная работа состоит в предоставлении вам помощи в получении сущностей определённого класса. Вы иожете получить доступ к объекту-репозиторию для класса-сущности через:

1
$repository = $em->getRepository('AppBundle:Product');

Note

Строка AppBundle:Product - это сокращение, которое можно использовать в Doctrine вместо полного имени класса для сущности (например, AppBundle\Entity\Product). Оно будет работать, пока ваша сущность находится в простанстве имён Entity вашего пакета.

Когда у вас уже имеется объект-репозиторий, то у вас есть доступ ко всяческим полезным методам:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$repository = $em->getRepository('AppBundle:Product');

// запрос по первичному ключу (обычно "id")
$product = $repository->find($productId);

// динамические имена методов, использующиеся для поиска одного товара по значению столбцов
$product = $repository->findOneById($productId);
$product = $repository->findOneByName('Клавиатура');

// динамические имена методов, использующиеся для поиска группы продуктов по значению столбцов
$products = $repository->findByPrice(19.99);

// найти *все* продукты
$products = $repository->findAll();

Note

Конечно, вы также можете задавать сложные запросы, о которых вы узнаете больше в разделе Запрашивание объектов.

Также можно использовать преимущества полезных методов findBy() и findOneBy() для лёгкого извлечения объектов, основываясь на нескольких условиях:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$repository = $em->getRepository('AppBundle:Product');

// запрос одного товара, подходящего по заданным имени и цене
$product = $repository->findOneBy(
    array('name' => 'Keyboard', 'price' => 19.99)
);

// запрос всех продуктов, подходящих по имени и отсортированных по цене
$products = $repository->findBy(
    array('name' => 'Keyboard'),
    array('price' => 'ASC')
);

Tip

Когда отображение страницы требует некоторых вызовов базы данных, панель инструментов веб-отладки внизу страницы отображает количество запросов и время, которое было потрачено на их выполнение:

_images/doctrine_web_debug_toolbar.png

Если число запросов в базу данных слишком высокое, иконка станет желтой, чтобы обозначить, что что-то может быть не так. Нажмите на иконку, чтобы открыть профилировщик Symfony (Profiler) и посмотрите, какие именно запросы были выполнены.

Обновление объекта

Когда вы получили объект из Doctrine, обновить его просто. Предположим, у вас есть маршрут, связывающий id товара с действием обновления в контроллере:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use Doctrine\ORM\EntityManagerInterface;

public function updateAction($productId, EntityManagerInterface $em)
{
    $product = $em->getRepository('AppBundle:Product')->find($productId);

    if (!$product) {
        throw $this->createNotFoundException(
            'Нет товара с id ' . $productId
        );
    }

    $product->setName('Новое имя товара!');
    $em->flush();

    return $this->redirectToRoute('homepage');
}

Обновление объекта включает в себя всего три шага:

  1. получение объкта из Doctrine;
  2. изменение объекта;
  3. вызов flush() из менеджера сущностей.

Заметьте, что в вызове $em->persist($product) нет необходимости. Вспомните, что этот метод лишь сообщает Doctrine что нужно управлять или "наблюдать" за объектом $product. В данной ситуации, так как объект $product получен из Doctrine, он уже является управляемым.

Удаление объекта

Удаление объекта очень похоже, но требует вызова метода remove() из менеджера сушностей:

1
2
$em->remove($product);
$em->flush();

Как и ожидалось, метод remove() уведомляет Doctrine о том, что вам хочется удалить указанный объект из базы данных. Тем не менее, фактический запрос DELETE не вызывается до тех пор, пока не запущен метод flush().

Запрашивание объектов

Вы уже видели, как объект-репозиторий позволяет вам выполнять базовые запросы без каких-либо усилий:

1
2
3
4
$repository = $em->getRepository('AppBundle:Product');

$product = $repository->find($productId);
$product = $repository->findOneByName('Keyboard');

Конечно, Doctrine также позволяет вам писать более сложные запросы, используя язык запросов Doctrine (DQL: Doctrine Query Language). DQL похож на SQL за исключением того, что следует представить, что запрашиваются один или несколько объектов из класса-сущности (например, Product) вместо строк из таблицы (например, product).

Запрашивать из Doctrine можно двумя основными способами: написанием чистых DQL-запросов, либо использованием конструктора запросов (query builder) Doctrine.

Запрашивание объектов через DQL

Представьте, что вы хотите запросить продукты, которые дороже, чем 19.99, упорядоченные от самого дешевого к самому дорогому. Вы можете использовать DQL, родной SQL-подобный язык Doctrine, для создания запроса для этой ситуации:

1
2
3
4
5
6
7
8
$query = $em->createQuery(
    'SELECT p
    FROM AppBundle:Product p
    WHERE p.price > :price
    ORDER BY p.price ASC'
)->setParameter('price', 19.99);

$products = $query->getResult();

Если вам удобно с SQL, то DQL должен быть также интуитивно понятен. Наибольшее различие в том, что надо думать в рамках PHP-объектов, вместо строк в базе данных. По этой причине, вы выбираете из сущности AppBundle:Product (опциональное сокращение для класса AppBundle\Entity\Product) и присваиваете ей псевдоним (alias) p.

Tip

Обратите внимание на метод setParameter(). Работая с Doctrine, всегда хорошо устанавливать внешние значения в качестве «заместителей» (placeholders), как :price в примере выше - это предотвращает атаки вида SQL-инъекции.

Метод getResult() возвращает массив результатов. Если же нужен лишь один результат, вы можете использовать getOneOrNullResult():

1
$product = $query->setMaxResults(1)->getOneOrNullResult();

Синтаксис DQL очень мощный, и позволяет вам с легкостью объединять сущности (тема отношений будет раскрыта позже), группировать и т.д. Для более подробной информации смотрите официальную документацию Язык запросов Doctrine.

Запрашивание объектов используя конструктор запросов Doctrine

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$repository = $em->getRepository('AppBundle:Product');

// createQueryBuilder() автоматически выбирает из AppBundle:Product
// и присваивает псевдоним "p"
$query = $repository->createQueryBuilder('p')
    ->where('p.price > :price')
    ->setParameter('price', '19.99')
    ->orderBy('p.price', 'ASC')
    ->getQuery();

$products = $query->getResult();
// чтобы получить только один результат:
// $product = $query->setMaxResults(1)->getOneOrNullResult();

Объект QueryBuilder содержит все необходимые методы для создания вашего запроса. Вызвав метод getQuery(), конструктор запросов вернёт обычный объект Query, якоторый можно использовать для получения результата запроса.

Для того, чтобы узнать больше о Конструкторе запросов Doctrine, смотрите документацию Конструктор запросов.

Организация специальных запросов в классы хранилища

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

См Репозиторий для информации.

Конфигурация

Doctrine очень гибкая, хотя вам, вероятно, никогда не придётся беспокоиться о большей части её опций. Чтобы узнать больше о настройке Doctrine, смотрите раздел Doctrine Справочник конфигурации.

Справка по типам полей в Doctrine

Doctrine имеет огромное количество типов полей. Каждый из них отображает данные PHP в определенном типе столбца в используемой вами базе данных. Для каждого типа поля, столбец (Column) можно настраивать дополнительно, устаналивая значения длины (length), поведения nullable, названия (name), и других опций. Чтобы увидеть весь перечень доступных типов, и узнать о них больше, смотрите документацию Doctrine Типы отображения.

Связи и объединения

Doctrine предоставляет весь функционал, который вам понадобится для управления связями базы данных (также известными как объединения). Для более детальной информации см. Объединения.

Заключение

Применяя Doctrine, вы можете сфокусироваться на объектах и их использовании в приложении, и только потом заботиться об их сохранении в базу данных. Это происходит благодаря тому, что Doctrine позволяет вам использовать любой объект PHP для хранения ваших данных, и опирается на информацию метаданных для отображения, чтобы отобразить эти данные об объекте в определённой таблице базы данных.

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

Узнайте больше

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.