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

Дата оновлення перекладу 2022-12-14

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

Screencast

Ви надаєте первагу відео-урокам? Подивіться Doctrine screencast series.

Symfony надає всі інструменти, які вам потрібні для використання баз даних у вашому додатку, завдяки Doctrine, найкращому набору PHP бібліотек для роботи з базами даних. Ці інструменти підтримують реляційні бази даних, такі як MySQL і PostgreSQL, а також NoSQL бази даних, такі як MongoDB

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

  • Ця стаття пояснює рекомендований спосіб роботи з реляційними базами даних у додатках Symfony;
  • Прочитайте цю статтю, якщо вам потрібен низькорівнений доступ для виконання напряму 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
# .env (або перевизначте DATABASE_URL в .env.local, щоб не додавати ваші зміни в репозиторій)

# налаштуйте цей рядок!
DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"

# для використання 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 зі значенням за замовчуванням 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 або USER). Дивіться документацію Doctrine Зарезервовані ключові слова SQL, щоб дізнаится деталі, так як екранувати їх. Або змініть назву таблиці @ORM\Table(name="groups") над класом або налаштуйте назву стовпчику в налаштуванні name="group_name".

Міграції: Створення таблиць/схеми бази даних

Клас Product повністю сконфігурований та готовий до збереження в таблицю product. Якщо ви щойно створили клас, ваша база даних ще не має таблиці product. Щоб додати її, можете використати попередньо встановлену DoctrineMigrationsBundle:

1
$ php bin/console make:migration

Якщо все спрацювало, то ви маєте побачити щось на кшталт:

УСПІХ!

Далі: Подивіться на нову міграцію "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

Чи може це поле бути в базі даних (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 dbal:run-sql 'SELECT * FROM product'

# у системах Windows, які не використовують Powershell, запустіть цю команду:
# php bin/console dbal:run-sql "SELECT * FROM product"

Розглянево попередній приклад детальніше:

  • Рядок 13 Аргумент ManagerRegistry $doctrine вказує Symfony впровадити сервіс Doctrine в метод контролера.
  • Рядок 15 Метод $doctrine->getManager() отримує об'єкт Doctrine менеджер сутностей, який є найважливішим об'єктом 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 використовує метадані 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();
        // This will trigger an error: the column isn't nullable in the database
        $product->setName(null);
        // This will trigger a type mismatch error: an integer is expected
        $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, всі ваші форми та веб-API будуть також автоматично отримувати користь від цих автоматичних обмежень валідації.

Автоматична валідація - це зручно та збільшує продуктивність, але вона не замінює повністю налаштування валідації. Вам все ще необхідно додати декілька обмежень валідації, щоб переконатися, що дані, надані користувачем, коректні.

Вилучення об'єктів з бази даних

Вилучення об'єкта з 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 з автомонтуванням Symfony та впровадити його через контейнер впровадження залежностей:

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

Коли ви запитуєте певний ти об'єкта, ви завжди використовуєте те, що відомо, як його "сховище". Ви можете думати про сховище як про PHP-клас, єдиною роботою якого є допомагати вам вилучати сутності певного класу.

Коли у вас є об'єкт сховища, у вас з'являється безліч методів-хелперів:

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-сторінки, панель інструментів веб-налагодження внизу сторінки відобразить кількість запитів та час, за який вони були виконані:

Якщо кількість запитів в базі даних занадто велика, іконка стане жовтою, щоб показати, що щось може бути не так. Натисніть на іконку, щоб відкрити Профільувальник Symfony та подивіться, які саме запити бути виконані. Якщо ви не бачите панелі інструментів веб-налагодження, встановіть profiler пакет Symfony , виконавши команду: 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} з маршруту, щоб запитати 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().

Запит об'єктів: Сховище

Ви вже бачили, як об'єкт сховище дозволяє вам виконувати базові запити без будь-яких зусиль:

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\Common\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($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. Це дозволяє вам створювати запити, використовуючи популярну мову запитів, але посилатися замість таблиць на PHP-об'єкты (наприклад у виразі FROM).

Тепер ви можете викликати цей метод в сховищі:

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

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

Див. , щоб дізнатися як впровадити сховище в будь-який сервіс.

Виконання запитів з конструктором запитів 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);
        $stmt->execute(['price' => $price]);

        // повертає масив масивів (тобто сирий набір даних)
        return $stmt->fetchAllAssociative();
    }
}

З SQL, ви отримаєте на виході сирі дані, а не об'єкти (окрім випадків, коли ви використовуєте функціональність NativeQuery).

Відносини та асоціації

Doctrine надає всі необхідні вам функції, щоб керувати відносинами бази даних (також відомими, як асоціації), включно з відносинами ManyToOne, OneToMany, OneToOne та ManyToMany.

Щоб дізнатися більше, див. Як працювати з асоціаціями / відносинами Doctrine.

Розширення Doctrine (Timestampable, Translatable, і т.д.)

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