Databases and the Doctrine
Базы данных и Doctrine ORM
Symfony предоставляет все необходимые инструменты для работы с базами данных в ваших приложениях благодаря Doctrine
Прочтите эту статью, если вам нужен низкоуровневый доступ для выполнения необработанных SQL-запросов к реляционным базам данных (аналогично PDO в PHP)
Установка Doctrine
composer require symfony/orm-pack
composer require --dev symfony/maker-bundle
Информация о подключении к базе данных хранится в переменной среды с именем DATABASE_URL
В приложениях Symfony доступны миграции с помощью DoctrineMigrationsBundle, который использует внешнюю библиотеку Doctrine Database Migrations
Документация по библиотеке Doctrine Database Migrations
Файл конфигурации config/packages/doctrine_migrations.yaml создается автоматически или вручную
composer require doctrine/doctrine-migrations-bundle "^3.0"
Источник: https://symfony.com/bundles/DoctrineMigrationsBundle/current/index.html
Настройка Doctrine ORM
В процессе установки пакетов Doctrine, будет добавлено несколько новых каталогов: migrations/ src/Entity/ и src/Repository/
Конфигурация почти всех установленных пакетов находится в директории config/packages/
config/packages/doctrine.yaml - конфигурационный файл Doctrine, в котором находятся параметры подключения
Основным параметром является DSN-строка (Data Source Name — "имя источника данных"), которая содержит информацию о подключении: учётные данные, хост, порт и т.д. По умолчанию Doctrine ищет переменную среды DATABASE_URL.
Переменную DATABASE_URL можно инициализировать в файлах .env или .env.local
Консольные команды DoctrineMigrationsBundle
doctrine:migrations:current [current] Outputs the current version.
doctrine:migrations:diff [diff] Generate a migration by comparing your current database to your mapping information.
doctrine:migrations:dump-schema [dump-schema] Dump the schema for your database to a migration.
doctrine:migrations:execute [execute] Execute a single migration version up or down manually.
doctrine:migrations:generate [generate] Generate a blank migration class.
doctrine:migrations:latest [latest] Outputs the latest version number
doctrine:migrations:migrate [migrate] Execute a migration to a specified version or the latest available version.
doctrine:migrations:rollup [rollup] Roll migrations up by deleting all tracked versions and inserting the one version that exists.
doctrine:migrations:status [status] View the status of a set of migrations.
doctrine:migrations:up-to-date [up-to-date] Tells you if your schema is up-to-date.
doctrine:migrations:version [version] Manually add and delete migration versions from the version table.
doctrine:migrations:sync-metadata-storage [sync-metadata-storage] Ensures that the metadata storage is at the latest version.
doctrine:migrations:list [list-migrations] Display a list of all available migrations and their status.
Пропуск миграций
Вы можете пропустить отдельные миграции, явно добавив их в таблицу migration_versions:
Doctrine предположит, что эта миграция уже была выполнена, и проигнорирует ее.
php bin/console doctrine:migrations:version 'App\Migrations\Version123' --add
Источник: https://symfony.com/bundles/DoctrineMigrationsBundle/current/index.html
Автоматическое создание миграций
Библиотека для работы с миграциями может автоматически генерировать классы миграций, сравнивая информацию с базой данных
Предположим, что вы создаете новую сущность User (src/Entity/User.php)
Doctrine готова помочь вам сохранить новый User объект в таблице user и извлечь его оттуда.
php bin/console doctrine:migrations:diff
На основе различий в схемах был создан новый класс миграции, в нем содержится SQL-код, необходимый для создания таблицы user
php bin/console doctrine:migrations:migrate
Источник: https://symfony.com/bundles/DoctrineMigrationsBundle/current/index.html
Ручные таблицы, не управляемые Doctrine
По умолчанию такие таблицы будут помечены для удаления командой doctrine:migrations:diff
Можно настроить doctrine/dbal для игнорирования некоторых таблиц:
# config/packages/doctrine.yaml
doctrine:
dbal:
schema_filter: ~^(?!t_)~ # Ignore all tables prefixed by `t_`
Источник: https://symfony.com/bundles/DoctrineMigrationsBundle/current/index.html
Создание пользователя и базы данных
#Создать роль в базе данных
CREATE ROLE user1;
#Добавить пароль для пользователя
ALTER ROLE user1 WITH LOGIN PASSWORD 'password';
#Создать базу данных
CREATE DATABASE db1 OWNER user1 ENCODING = 'UTF-8' LC_COLLATE = 'ru_RU.UTF-8' LC_CTYPE = 'ru_RU.UTF-8';
DATABASE_URL="postgresql://user1:password@127.0.0.1:5432/db1?serverVersion=16&charset=utf8"
Если имя пользователя, пароль, имя хоста или базы данных содержат специальные символы, используемые в URI (например, : / ? # [ ] @ ! $ & ' ( ) * + , ; =), их необходимо закодировать. Полный список зарезервированных символов приведен в RFC 3986. Для кодирования можно использовать функцию urlencode или процессор переменных окружения urlencode. В этом случае необходимо удалить префикс resolve: в config/packages/doctrine.yaml, чтобы избежать ошибок: url: '%env(DATABASE_URL)%'
Чтобы избежать проблем с URL-кодированием при использовании специальных символов в учетных данных, можно использовать отдельные параметры подключения вместо формата URL. Определите каждое значение как отдельную переменную среды и заключите его в одинарные кавычки в файле .env, чтобы такие символы, как $ и #, не интерпретировались:
DATABASE_PASSWORD='p@ss$wo#rd'
Затем настройте Doctrine на использование отдельных параметров:
# config/packages/doctrine.yaml
doctrine:
dbal:
user: '%env(DATABASE_USER)%'
password: '%env(DATABASE_PASSWORD)%'
host: '%env(DATABASE_HOST)%'
port: '%env(DATABASE_PORT)%'
dbname: '%env(DATABASE_NAME)%'
driver: pdo_pqsql
Существует множество других команд Doctrine. Запустите php bin/console list doctrine , чтобы увидеть полный список.
Источник: https://symfony.com/doc/current/doctrine.html
Создание класса сущностей
С помощью команды make:entity вы можете создать класс сущности и необходимые поля в интерактивном режиме
php bin/console make:entity
После подтверждения будет создан файл src/Entity/Profile.php
Этот класс называется «сущностью». Теперь можно сохранять объекты Profile и запрашивать их из profile таблицы в своей базе данных. Каждое свойство Product сущности можно сопоставить со столбцом в этой таблице. Обычно это делается с помощью атрибутов — #[ORM\Column(...)] комментариев, которые видно над каждым свойством.
Вы можете передать --with-uuid или --with-ulid в make:entity. Используя компонент Uid Symfony, вы можете создать сущность с типом id как Uuid или Ulid вместо int.
Типы полей сущностей
Ознакомьтесь с перечнем типов сопоставления Doctrine
https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/basic-mapping.html#reference-mapping-types
Один из таких типов основан на перечисляемых типах данных PHP, которые позволяют определить закрытый набор возможных значений для данного типа. Это делает их подходящим решением для моделирования свойств сущностей, которые могут принимать только заранее определенный набор значений.
Подробнее: https://symfony.com/doc/current/doctrine.html#entity-field-types
Миграции: создание таблиц/схемы базы данных
#Для создания миграции
php bin/console make:migration
created: migrations/Version20260507165112.php
Success!
Review the new migration then run it with php bin/console doctrine:migrations:migrate
#Для создания сущности (таблицы) в базе данных
#Выполнить миграцию
php bin/console doctrine:migrations:migrate
WARNING! You are about to execute a migration in database "apimega" that could result in schema changes and data loss. Are you sure you wish to continue? (yes/no) [yes]:
> yes
[notice] Migrating up to DoctrineMigrations\Version20260507165112
[notice] finished in 16.2ms, used 22M memory, 1 migrations executed, 3 sql queries
[OK] Successfully migrated to version: DoctrineMigrations\Version20260507165112
Эта команда запускает все файлы миграции, которые еще не были применены к вашей базе данных. Эту команду следует запускать при развертывании рабочей среды, чтобы поддерживать базу данных в актуальном состоянии.
Миграции и добавление новых полей
Можно отредактировать класс сущности, чтобы добавить новое свойство. Но также можно снова использовать make:entity:
php bin/console make:entity
Class name of the entity to create or update
> Profile
В процессе создания нового свойства, в класс Profile будет добавлено свойство и методы set*() get*()
После редактирования класс необходимо сгенерировать новую миграцию
Система миграции умна. Она сравнивает все ваши сущности с текущим состоянием базы данных и генерирует SQL-запросы, необходимые для их синхронизации
#Создать миграцию
php bin/console make:migration
#Выполнить миграцию
php bin/console doctrine:migrations:migrate
Если вы предпочитаете добавлять новые свойства вручную, команда make:entity может сгенерировать для вас методы получения и установки значений.
php bin/console make:entity --regenerate
Если вы внесли какие-то изменения и хотите перегенерировать все методы получения и установки значений, также передайте --overwrite.
Источник: https://symfony.com/doc/current/doctrine.html#migrations-adding-more-fields
Создание контроллера сущности и сохранение в базе данных
#Создать контроллер
php bin/console make:controller ProfileController
Do you want to generate PHPUnit tests? [Experimental] (yes/no) [no]:
>
created: src/Controller/ProfileController.php
created: templates/profile/index.html.twig
Success!
#src/Controller/ProfileController.php
namespace App\Controller;
use App\Entity\Profile;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class ProfileController extends AbstractController
{
#[Route('/profile', name: 'add_profile')]
public function createProfile(EntityManagerInterface $entityManager): Response
{
$profile = new Profile();
$profile->setNameUser('Admin');
$profile->setPasswordUser('password');
//сохранить объект Product (запросы пока не выполняются)
$entityManager->persist($profile);
//фактически выполняет запросы (например, запрос INSERT)
$entityManager->flush();
return new Response($profile->getId());
}
}
Аргумент EntityManagerInterface $entityManager указывает Symfony на необходимость внедрить сервис Entity Manager в метод контроллера. Этот объект отвечает за сохранение объектов в базе данных и их извлечение из нее.
Вызов persist($product) указывает Doctrine на необходимость «управления» объектом
При вызове метода flush() Doctrine просматривает все объекты, которыми она управляет, чтобы определить, нужно ли сохранять их в базе данных. В этом примере данных объекта $profile нет в базе данных, поэтому менеджер сущностей выполняет INSERT запрос, создавая новую строку в таблице profile
Независимо от того, создаете вы объекты или обновляете их, процесс всегда один и тот же: Doctrine достаточно умен, чтобы определить, нужно ли выполнить INSERT или UPDATE для вашего объекта.
После перехода на страницу /profile, будет добавлен пользователь в таблицу базы данных
Проверить наличие данной строки можно с помощью следующей команды
php bin/console dbal:run-sql 'SELECT * FROM profile'
Связывание сущностей
Созданные сущности, конференция и комментарий, должны быть взаимосвязаны. Например сущности User, Blog, Comment, то есть комментарий будет принадлежать определенному блогу и пользователю
Используйте команду make:entity ещё раз, чтобы добавить эту связь в существующий класс сущности, но только если свойство не существует
В интерактивном режиме, если в качестве ответа на вопрос о типе данных вы введёте ?, то вы получите список всех поддерживаемых типов, где в качестве типа можно выбрать тип связи и существующий тип сущности
symfony console make:entity Conference
Your entity already exists! So let's add some new fields!
New property name (press to stop adding fields):
> comments
Field type (enter ? to see all types) [string]:
> OneToMany
What class should this entity be related to?:
> Comment
A new property will also be added to the Comment class...
В результате выполнения данной команды в класс сущности будет добавлено новое свойство типа Comment и соответствующие ему атрибуты
Отношения doctrine
Существует два основных типа отношений/связей:
ManyToOne / OneToMany
ManyToMany - необходима в тех случаях, когда обе стороны связи могут иметь множество элементов другой стороны (например классы и ученики)
ManyToOne - например мы создаем сущность comments (комментарии) и добавляем в его класс свойство posts (посты) класса Post с типом отношения ManyToOne (много комментариев к одному посту)
OneToMany - в классе Post (посты) создаем свойство comments класса Comment c типом отношения OneToMany (один пост к многим комментариям)
При создании сущностей Doctrine обеспечивает сохранение этих связей.
....
use App\Entity\Post;
use App\Entity\Comment;
....
#[Route('/post', name: 'posts')]
public function index(EntityManagerInterface $entityManager): Response
{
$post = new Post();
$post->setName('Computer Peripherals');
$comment = new Comment();
$comment->setText('This text ...');
$post->setComments($comment);
$entityManager->persist($post);
$entityManager->persist($comment);
$entityManager->flush();
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
....
Выборка связанных объектов
В этом примере сначала находим пост по его id, далее получаем все связанные с ним комментарии
use App\Entity\Product;
// ...
class PostController extends AbstractController
{
public function show(PostRepository $postRepository, int $id): Response
{
$post = $postRepository->find($id);
// ...
$comments = $post->getComments()->getText();
// ...
}
}
Поскольку мы сопоставили необязательную часть OneToMany, вы также можете выполнять запросы в обратном направлении
class PostController extends AbstractController
{
public function show(CommentRepository $commentRepository, int $id): Response
{
$comment = $commentRepository->find($id);
// ...
$posts = $comment->getPosts()->getName();
// ...
}
}
Объединение связанных записей
Если вы заранее знаете, что вам понадобится доступ к обоим объектам, можно избежать второго запроса, выполнив объединение в исходном запросе. Добавьте в ProductRepository следующий метод:
// src/Repository/ProductRepository.php
// ...
class ProductRepository extends ServiceEntityRepository
{
public function findOneByIdJoinedToCategory(int $productId): ?Product
{
$entityManager = $this->getEntityManager();
$query = $entityManager->createQuery(
'SELECT p, c
FROM App\Entity\Product p
INNER JOIN p.category c
WHERE p.id = :id'
)->setParameter('id', $productId);
return $query->getOneOrNullResult();
}
}
Теперь вы можете использовать этот метод в своем контроллере для запроса Product объекта и связанных с ним Category в рамках одного запроса:
// src/Controller/ProductController.php
// ...
class ProductController extends AbstractController
{
public function show(ProductRepository $productRepository, int $id): Response
{
$product = $productRepository->findOneByIdJoinedToCategory($id);
$category = $product->getCategory();
// ...
}
}
Более подробную информацию и примеры использования других типов связей (например, «один к одному», «многие ко многим») можно найти в документации Doctrine по отображению ассоциаций
https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/association-mapping.html
Источник: https://symfony.com/doc/current/doctrine/associations.html#joining-related-records






