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

Дата обновления перевода: 2023-01-18

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

Screencast

Вы предпочитаете видео-уроки? Посмотрите Doctrine screencast series.

Symfony предоставляет все инструменты, которые вам нужны для использования баз данных в вашем приложении благодаря Doctrine, лучшему набору PHP библиотек для работы с базами данных. Эти инструменты поддерживают реляционные базы данных такие как MySQL и PostgreSQL, а также NoSQL базы данных такие как MongoDB

Базы данных - это широкая тема, поэтому документация разделена на три статьи:

  • Эта статья объясняет рекомендованый способ работы с реляционными базами данных в приложениях Symfony;
  • Прочитайте о DBAL если вам нужен низкоуровневый доступ для выполнения напрямую SQL запросов в реляционные базы данных (похоже на PDO в PHP);
  • Прочитайте документацию DoctrineMongoDBBundle, если вы работаете с базами данных MongoDB.

Установка Doctrine

Сначала установите поддержку Doctrine через orm Symfony pack , вместе с MakerBundle, которая поможет генерировать код:

1
2
$ composer require symfony/orm-pack
$ composer require --dev symfony/maker-bundle

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

Информация соединения DB хранится в переменной окружения DATABASE_URL. Для разработки вы можете найти и установить её внутри .env:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# .env (или переопределите DATABASE_URL в .env.local чтобы не добавлять ваши изменения в репозиторий)

# настройте эту строчку!
DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"

# для использования mariadb:
DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=mariadb-10.5.8"

# для использования sqlite:
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"

# для использования postgresql:
# DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8"

# для использования oracle:
# DATABASE_URL="oci8://db_user:db_password@127.0.0.1:1521/db_name"

Caution

Если имя пользователя, пароль, хост или название базы данных содержат один из символов, которые считаются специальными в URI (такие как +, @, $, #, /, :, *, !), вы должны их экранировать. См. RFC 3986 для полного списка зарезервированных символов или используйте функцию urlencode для их экранирования. В этом случае вам нужно удалить префикс resolve: в config/packages/doctrine.yaml для избежания ошибок: url: '%env(resolve:DATABASE_URL)%'

Теперь, когда ваши параметры соединения настроены, Doctrine может создать для вас DB db_name:

1
$ php bin/console doctrine:database:create

Существует больше опций в config/packages/doctrine.yaml, которые вы можете настроить, включая вашу server_version (например, 5.7, если вы используете MySQL 5.7), которые могут повлиять на то, как функционирует Doctrine.

Tip

Существует много других команд Doctrine. Запустите php bin/console list doctrine, чтобы увидеть полный список.

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

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

Используйте команду make:entity, чтобы создать этот класс и все поля, которые вам нужны. Команда задаст несколько вопросов - ответьте на них как в примере ниже:

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
$ php bin/console make:entity

Имя класаа сущности для создания или обновления:
> Product

Новое имя свойства (нажмите <return>, чтобы перестать добавлять поля):
> name

Тип поля (введите ?, чтобы увидеть все типы) [string]:
> string

Длина поля [255]:
> 255

Может ли это поле быть null в базе данных (nullable) (да/нет) [no]:
> no

Новое имя свойства (нажмите <return>, чтобы перестать добавлять поля):
> price

Тип поля (введите ?, чтобы увидеть все типы) [string]:
> integer

Может ли это поле быть null в базе данных (nullable) (да/нет) [no]:
> no

Новое имя свойства (нажмите <return>, чтобы перестать добавлять поля):
>
(нажмите enter снова, чтобы закончить)

Ух ты! Теперь у вас есть новый файл src/Entity/Product.php:

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
// src/Entity/Product.php
namespace App\Entity;

use App\Repository\ProductRepository;
use Doctrine\ORM\Mapping as ORM;

 #[ORM\Entity(repositoryClass: ProductRepository::class)]
class Product
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private int $id;

    #[ORM\Column(length: 255)]
    private string $name;

    #[ORM\Column]
    private int $price;

    public function getId(): ?int
    {
        return $this->id;
    }

    // ... методы геттера и сеттера
}

Note

Начиная с v1.44.0 - MakerBundle поддерживает только сущности, использующие PHP-атрибуты.

Note

Удивлены почему цена это целое число? Не переживайте: это лишь пример. Но, если хранить цены как целые числа (например 100 = $1) можно избежать проблем с округлением.

Note

Если вы используете базу данных SQLite, вы увидите следующую ошибку: PDOException: SQLSTATE[HY000]: General error: 1 Невозможно добавить столбец NOT NULL cо значением по умолчанию NULL. Добавьте опцию nullable=true к свойству description, чтобы устранить проблему.

Caution

Существует лимит в 767 байтов для индекса, когда используются таблицы InnoDB в MySQL 5.6 или более ранние версии. Строковые колонки с длиной 255 символов и кодировкой utf8mb4 превышают этот лимит. Это значит, что любая колонка типа string и unique=true должна иметь максимальную length 190. Иначе отобразится ошибка: "[PDOException] SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes".

Этот класс называется "сущность". И вскоре вы сможете сохранять и запрашивать объекты Product в таблице product в вашей базе данных. Каждое свойство в сущности Product может быть связано с колонкой в этой таблице. Это обычно делается аннотациями: комментарии #[ORM\Column(...)], которые вы видите над каждым свойством:

Команда make:entity - это инструмент упрощающий жизнь. Но это ваш код: добавляйте/удаляйте поля, добавляйте/удаляйте методы или обновляйте конфигурацию.

Doctrine поддержвивет множество типов полей, каждое со своими настройками. Весь список можно посмотреть в документации отображения типов Doctrine. Если вы хотите использовать XML вместо аннотаций, добавьте type: xml и dir: '%kernel.project_dir%/config/doctrine' к маппингу сущностей в вашем файле `config/packages/doctrine.yaml``.

Caution

Будьте осторожны и не используйте зарезервированые ключевые слова SQL для названия таблиц или столбцов (например, GROUP or USER). Смотрите документацю Doctrine Зарезериврованные ключевые слова SQL для деталей или как экранировать их. Или измените название таблицы @ORM\Table(name="groups") над классом или настройте название столбца в настройке name="group_name".

Миграции: Создание таблиц / схемы базы данных

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

1
$ php bin/console make:migration

Если всё сработало, то вы должны увидеть что-то вроде:

1
2
3
4
УСПЕШНО!

Далее: Посмотрите на новую миграцию "migrations/Version20211116204726.php"
Затем: Запустите миграцию с помощью php bin/console doctrine:migrations:migrate

Если вы откроете этот файл, то увидите, что он содержит SQL, необходимый для обновленя вашей DB! Чтобы запустить этот SQL, выполните ваши миграции:

1
$ php bin/console doctrine:migrations:migrate

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

Миграции и добавление дополнительных полей

Но что, если вам нужно добавить новое свойство поля в Product, например, description? Вы можете отредактировать класс и добавить новое свойство. Но можете также запустить снова make:entity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ php bin/console make:entity

Имя класса сущности для создания или обновления
> Product

Новое имя свойства (нажмите <return>, чтобы перестать добавлять поля):
> description

Тип поля (введите ?, чтобы увидеть все типы) [string]:
> text

Может ли это поле быть in в базе данных (nullable) (да/нет) [no]:
> no

Новое имя свойства (нажмите <return>, чтобы перестать добавлять поля):
>
(нажмите enter снова, чтобы закончить)

Это также добавит новое свойство description и методы getDescription() и setDescription():

1
2
3
4
5
6
7
8
9
10
11
12
// src/Entity/Product.php
  // ...

  class Product
  {
      // ...

+     #[ORM\Column(type: 'text')]
+     private $description;

      // getDescription() и setDescription() также были добавлены
  }

Новое свойство связано с базой данных, но оно ещё не существует в таблице product. Сгенерируйте новую миграцию:

1
$ php bin/console make:migration

В этот раз SQL в сгенерированном файле будет выглядеть так:

1
ALTER TABLE product ADD description LONGTEXT NOT NULL

Система миграций умная. Она сравнивает все ваши сущности с текущим состоянием базы данных и генерирует SQL необходимый для их синхронизации! Как и раньше, примените ваши миграции:

1
$ php bin/console doctrine:migrations:migrate

Это выполнит только один новый файл миграции, так как DoctrineMigrationsBundle знает, что первая миграция уже была выполнена ранее. За кулисами, она автоматически управляет таблицей migration_versions, чтобы следить за этим.

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

Tip

Если вы предпочитаете добавлять новые свойства вручную, команда make:entity может генерировать методы геттеров и сеттеров для вас:

1
$ php bin/console make:entity --regenerate

Если мы делаете изменения и хотите перегенерировать все методы геттеров/сеттеров, также укажите --overwrite.

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

Пора сохранить объект Product в базу данных! Давайте создадим новый контроллер для экспериментов:

1
$ php bin/console make:controller ProductController

Внутри контроллера, вы можете создать новый объект Product, установить данные в нём и сохранить его!:

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
// src/Controller/ProductController.php
namespace App\Controller;

// ...
use App\Entity\Product;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class ProductController extends AbstractController
{
    #[Route('/product', name: 'create_product')]
    public function createProduct(ManagerRegistry $doctrine): Response
    {
        $entityManager = $doctrine->getManager();

        $product = new Product();
        $product->setName('Keyboard');
        $product->setPrice(1999);
        $product->setDescription('Ergonomic and stylish!');

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

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

        return new Response('Saved new product with id '.$product->getId());
    }
}

Попробуйте!

http://localhost:8000/product

Поздравляем! Вы только что создали вашу первую строку в таблице product. Чтобы доказать это, вы можете запросить DB напрямую:

1
2
3
4
$ php bin/console doctrine:query:sql 'SELECT * FROM product'

# в системах Windows, не использующих Powershell, запустите эту команду:
# php bin/console doctrine:query:sql "SELECT * FROM product"

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

  • Строка 14 Аргумент ManagerRegistry $doctrine указывает Symfony внедрить сервис Doctrine в метод контроллера.
  • Строка 15 Метод $doctrine->getManager() получает объект Doctrine менеджер сущностей (entity manager), который является самым важным объектом Doctrine. Он отвечает за сохранение в базу данных, и получение объектов из базы данных.
  • Строки 17-20 В этой части вы создаёте объект $product и работаете с ним, как и с любым другим обычным PHP-объектом.
  • Строка 23 Вызов persist($product) сообщает Doctrine, чтобы он "управлял" объектом $product. Это не создает запрос в базу данных.
  • Строка 26 Когда вызывается метод flush(), Doctrine просматривает все объекты, которыми она управляет, чтобы узнать, нужно ли их сохранять в базу данных. В этом примере, объект $product не существует в базе данных, так что менеджер сущностей выполняет запрос INSERT, создавая новую строку в таблице product.

Note

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

И для создания, и для обновления объектов, рабочий процесс всегда одинаков: Doctrine достаточно умна для того, чтобы знать, что делать с вашей сущностью: INSERT или UPDATE.

Валидация объектов

Symfony validator повторно использует метаданные Doctrine для осуществления базовых заданий валидации:

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
// src/Controller/ProductController.php
namespace App\Controller;

use App\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
// ...

class ProductController extends AbstractController
{
    #[Route('/product', name: 'create_product')]
    public function createProduct(ValidatorInterface $validator): Response
    {
        $product = new Product();
        // Это вызовет ошибку: столбец не является nullable в базе данных
        $product->setName(null);
        // Это вызовет ошибку рассогласования: ожидается целое число
        $product->setPrice('1999');

        // ...

        $errors = $validator->validate($product);
        if (count($errors) > 0) {
            return new Response((string) $errors, 400);
        }

        // ...
    }
}

Несмотря на то, что сущность Product не объявляет явной конфигурации валидации, Symfony просматривает настройки Doctrine для применения некоторых правил валидации. Например, так как свойство name не может быть null в базе данных, то ограничение NotNull автоматически добавляется к свойству (если оно уже не содержало это ограничение).

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

???????? Doctrine ??????????? ????????? ???????
nullable=false NotNull ??????? ????????? ?????????? PropertyInfo
type Type ??????? ????????? ?????????? PropertyInfo
unique=true UniqueEntity  
length Length  

Так как компонент Form также как и API Platform внутри себя используют компонент Validator, все ваши формы и web API будут также автоматически получать пользу от этих автоматических ограничений валидации.

Автоматическая валидация - это удобно и увеличивает продуктивность, но она не заменяет полностью настройку валидации. Вам всё ещё нужно добавить несколько ограничений валидации (validation constraints), чтобы убедиться, что данные, предоставляемые пользователем, корректны.

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

Извлечение объекта обратно из DB ещё проще. Представьте, что вы хотите перейти в /product/1, чтобы увидеть ваш новый товар:

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
// src/Controller/ProductController.php
namespace App\Controller;

use App\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
// ...

class ProductController extends AbstractController
{
    #[Route('/product/{id}', name: 'product_show')]
    public function show(ManagerRegistry $doctrine, int $id): Response
    {
        $product = $doctrine->getRepository(Product::class)->find($id);

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

        return new Response('Check out this great product: '.$product->getName());

        // или отобразить шаблон
        // в шаблоне, печатайте все с {{ product.name }}
        // вернет $this->render('product/show.html.twig', ['product' => $product]);
    }
}

Также можно использовать ProductRepository с autowiring Symfony и внедрить его через dependency injection container:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Controller/ProductController.php
namespace App\Controller;

use App\Entity\Product;
use App\Repository\ProductRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
// ...

class ProductController extends AbstractController
{
    #[Route('/product/{id}', name: 'product_show')]
    public function show(int $id, ProductRepository $productRepository): Response
    {
        $product = $productRepository
            ->find($id);

        // ...
    }
}

Попробуйте!

http://localhost:8000/product/1

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

Когда у вас есть объект хранилища, у вас появляется множество helper-методов:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$repository = $doctrine->getRepository(Product::class);

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

// искать один Продукт по имени
$product = $repository->findOneBy(['name' => 'Keyboard']);
// или по имени и цене
$product = $repository->findOneBy([
    'name' => 'Keyboard',
    'price' => 1999,
]);

// искать несколько объектов Продуктов соответствующих имени, упорядоченные по цене
$products = $repository->findBy(
    ['name' => 'Keyboard'],
    ['price' => 'ASC']
);

// искать *все* объекты Продуктов
$products = $repository->findAll();

Вы можете также добавлять пользовательские методы для более сложных запросов! Больше об этом вы узнаете позже, в разделе .

Tip

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

Если количество запросов в DB слишком велико, иконка станет жёлтой, чтобы показать, что что-то может быть не так. Нажмите на иконку, чтобы открыть Symfony Profiler и посмотрите, какие именно запросы были выполнены. Если вы не видите панели инструментов веб-отладки, установите profiler Symfony pack запустив команду: composer require --dev symfony/profiler-pack.

Автоматическое извлечение объектов (ParamConverter)

Во многих случаях, вы можете использовать SensioFrameworkExtraBundle, чтобы запрос был сделан за вас автоматически! Для начала, установите пакет, если у вас его нет:

1
$ composer require sensio/framework-extra-bundle

Теперь, упростите ваш контроллер:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Controller/ProductController.php
namespace App\Controller;

use App\Entity\Product;
use App\Repository\ProductRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
// ...

class ProductController extends AbstractController
{
    #[Route('/product/{id}', name: 'product_show')]
    public function show(Product $product): Response
    {
        // используйте Продукт!
        // ...
    }
}

Вот и всё! Пакет использует {id} из route, чтобы запросить Product по столбцу id. Если он не найден, генерируется страница 404.

Существует еще множество опций, которые вы можете использовать. Прочтите больше о ParamConverter.

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

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

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
// src/Controller/ProductController.php
namespace App\Controller;

use App\Entity\Product;
use App\Repository\ProductRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
// ...

class ProductController extends AbstractController
{
    #[Route('/product/edit/{id}', name: 'product_edit')]
    public function update(ManagerRegistry $doctrine, int $id): Response
    {
        $entityManager = $doctrine->getManager();
        $product = $entityManager->getRepository(Product::class)->find($id);

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

        $product->setName('New product name!');
        $entityManager->flush();

        return $this->redirectToRoute('product_show', [
            'id' => $product->getId()
        ]);
    }
}

Используя Doctrine для изменения объекта нужно сделать три шага:

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

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

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

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

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

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

Запрашивание объектов: Хранилище

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

1
2
3
// изнутри контроллера
$repository = $doctrine->getRepository(Product::class);
$product = $repository->find($id);

Но что, если вам нужен более сложный запрос? Когда вы сгенерировали свою сущность с помощью make:entity, команда также сгенерировала класс ProductRepository:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Repository/ProductRepository.php
namespace App\Repository;

use App\Entity\Product;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

class ProductRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Product::class);
    }
}

Когда вы извлекаете ваше хранилище (т.е. ->getRepository(Product::class)), оно на самом деле является экземпляром этого объекта! Это так из-за конфигурации repositoryClass, которая была создана поверх вашего класса сушности.

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

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
// src/Repository/ProductRepository.php

// ...
class ProductRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Product::class);
    }

    /**
     * @return Product[]
     */
    public function findAllGreaterThanPrice(int $price): array
    {
        $entityManager = $this->getEntityManager();

        $query = $entityManager->createQuery(
            'SELECT p
            FROM App\Entity\Product p
            WHERE p.price > :price
            ORDER BY p.price ASC'
        )->setParameter('price', $price);

        // возвращает массив объектов Продуктов
        return $query->getResult();
    }
}

Строка, передаваемая в createQuery(), может показаться похожей на SQL, но это Doctrine Query Language. Это позволяет вам создавать запросы используя популярный язык запросов, но ссылаться вместо таблиц на PHP-объекты (например в выражении FROM).

Теперь, вы можете вызать этот метод в repository:

1
2
3
4
5
6
// изнутри контроллера
$minPrice = 1000;

$products = $doctrine->getRepository(Product::class)->findAllGreaterThanPrice($minPrice);

// ...

См. , чтобы узнать как внедрить repository в любой сервис.

Выполнение запросов с конструктором запросов Query Builder

Doctrine также предоставляет Query Builder, объектно-ориентированный способ писать запросы. Рекомендуется использовать его, когда запросы создаются динамически (то есть, базируясь на условной логике в PHP):

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
// src/Repository/ProductRepository.php

// ...
class ProductRepository extends ServiceEntityRepository
{
    public function findAllGreaterThanPrice(int $price, bool $includeUnavailableProducts = false): array
    {
        // автоматически знает, что надо выбирать Продукты
        // "p" - это псевдоним, который вы будете использовать до конца запроса
        $qb = $this->createQueryBuilder('p')
            ->where('p.price > :price')
            ->setParameter('price', $price)
            ->orderBy('p.price', 'ASC');

        if (!$includeUnavailableProducts) {
            $qb->andWhere('p.available = TRUE');
        }

        $query = $qb->getQuery();

        return $query->execute();

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

Запросы с помощью SQL

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Repository/ProductRepository.php

// ...
class ProductRepository extends ServiceEntityRepository
{
    public function findAllGreaterThanPrice(int $price): array
    {
        $conn = $this->getEntityManager()->getConnection();

        $sql = '
            SELECT * FROM product p
            WHERE p.price > :price
            ORDER BY p.price ASC
            ';
        $stmt = $conn->prepare($sql);
        $resultSet = $stmt->executeQuery(['price' => $price]);

        // возвращает массив массивов (т.e. сырой набор данных)
        return $stmt->fetchAllAssociative();
    }
}

С SQL, вы получите на выходе сырые данные, а не объекты (кроме случаев, когда вы используете функциональность NativeQuery).

Отношения и ассоциации

Doctrine предоставляет все необходимые вам функции, чтобы управлять отношениями DB (также известными, как ассоциации), включая отношения ManyToOne, OneToMany, OneToOne и ManyToMany.

Чтобы узнать больше, см. Как работать с ассоциациями / отношениями Doctrine.

Расширения Doctrine (Timestampable, Translatable, etc.)

Сообщество Doctrine создало расширения для удовлетворения частых потребностей вроде "установить значение свойства createdAt автоматически при создании новой сущности". Читайте делальнее о доступных расширениях Doctrine и используйте StofDoctrineExtensionsBundle для из интеграции в ваше приложение.