Doctrine: первая сущность и первая миграция за 20 минут
Как поднять Doctrine ORM в PHP-проекте, описать первую сущность и создать таблицу через миграцию, а не руками в phpMyAdmin. Пошагово, с проверкой на каждом этапе.

К концу этого руководства у вас будет рабочая связка Doctrine ORM с базой данных, одна описанная сущность Product и таблица в базе, созданная миграцией — без единого CREATE TABLE, написанного руками. На чистом PHP-проекте это займёт минут двадцать, если PHP и Composer уже установлены. Проверено на Doctrine ORM 3.x, Doctrine Migrations 3.x и PHP 8.2.
Что нужно иметь до начала
Прежде чем открывать редактор, проверьте, что на месте всё из списка. Если чего-то нет, инструкция сломается не на первом шаге, а на третьем — и это сложнее диагностировать.
- PHP 8.1 или новее. Doctrine ORM 3.x требует минимум 8.1. Проверьте:
php -v. - Composer. Проверьте:
composer --version. - Расширение PDO для вашей СУБД:
pdo_mysqlдля MySQL/MariaDB илиpdo_pgsqlдля PostgreSQL. Проверьте:php -m | grep pdo. - Доступ к базе данных: имя базы, пользователь и пароль. База уже должна существовать — Doctrine создаёт таблицы, но не саму базу.
Разбирается только связка ORM плюс Migrations на «голом» PHP. Интеграция с Symfony через doctrine/doctrine-bundle — отдельная история, там конфигурация лежит в другом месте.
Шаг 1. Установка пакетов
В корне проекта выполните:
composer require doctrine/orm doctrine/dbal doctrine/migrations
Composer подтянет ORM, слой абстракции базы (DBAL) и инструмент миграций. После установки в папке vendor появится каталог doctrine, а в корне — файл composer.json с тремя новыми зависимостями. Если видите ошибку про версию PHP — обновите PHP, понижать версию Doctrine ради старого PHP не стоит.
Шаг 2. Bootstrap: подключаем EntityManager
Doctrine работает через объект EntityManager — через него вы сохраняете и читаете сущности. Создайте файл bootstrap.php в корне проекта.
<?php
// bootstrap.php
require_once __DIR__ . '/vendor/autoload.php';
use Doctrine\ORM\ORMSetup;
use Doctrine\ORM\EntityManager;
use Doctrine\DBAL\DriverManager;
// Пути к папкам с классами-сущностями
$config = ORMSetup::createAttributeMetadataConfiguration(
paths: [__DIR__ . '/src/Entity'],
isDevMode: true,
);
// Параметры подключения к базе
$connectionParams = [
'driver' => 'pdo_mysql',
'host' => '127.0.0.1',
'dbname' => 'shop',
'user' => 'shop_user',
'password' => 'YOUR_DB_PASSWORD',
];
$connection = DriverManager::getConnection($connectionParams, $config);
$entityManager = new EntityManager($connection, $config);
Метод createAttributeMetadataConfiguration говорит Doctrine, что метаданные сущностей вы описываете PHP-атрибутами, а не аннотациями или XML. В Doctrine ORM 3.x атрибуты — способ по умолчанию, аннотации из старых гайдов уже удалены. Как понять, что шаг удался: файл создан, синтаксических ошибок нет — проверьте командой php -l bootstrap.php, должно вывести No syntax errors detected.
Шаг 3. Первая сущность
Сущность — это обычный PHP-класс, размеченный атрибутами Doctrine. Создайте файл src/Entity/Product.php.
<?php
// src/Entity/Product.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'products')]
class Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\Column(type: 'string', length: 255)]
private string $name;
#[ORM\Column(type: 'integer')]
private int $price;
public function getId(): ?int
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): void
{
$this->name = $name;
}
public function getPrice(): int
{
return $this->price;
}
public function setPrice(int $price): void
{
$this->price = $price;
}
}
Атрибут #[ORM\Id] помечает первичный ключ, #[ORM\GeneratedValue] — что база сама выдаёт значение (auto increment). Цену держим в целых копейках, а не во float: деньги во float — классический источник ошибок округления. Как проверить шаг: php -l src/Entity/Product.php без ошибок.
Сущность не знает, в какой таблице лежит и как называются её колонки, пока вы не разметите её атрибутами. Doctrine читает эти метаданные и сама строит SQL — в этом весь смысл ORM.
Шаг 4. Настройка Migrations
Инструмент миграций запускается отдельной консольной командой и требует двух вещей: знать, как подключиться к базе, и знать, где искать сущности. Он не читает ваш bootstrap.php напрямую — ему нужен свой мост.
Создайте migrations-db.php в корне — файл возвращает параметры подключения:
<?php
// migrations-db.php
return [
'driver' => 'pdo_mysql',
'host' => '127.0.0.1',
'dbname' => 'shop',
'user' => 'shop_user',
'password' => 'YOUR_DB_PASSWORD',
];
Дальше нужен консольный вход, который отдаёт инструменту готовый EntityManager. Создайте cli-config.php — Doctrine ищет его в корне проекта:
<?php
// cli-config.php
use Doctrine\Migrations\Configuration\EntityManager\ExistingEntityManager;
use Doctrine\Migrations\Configuration\Migration\PhpFile;
use Doctrine\Migrations\DependencyFactory;
require __DIR__ . '/bootstrap.php'; // отсюда берём $entityManager
$migrationsConfig = new PhpFile(__DIR__ . '/migrations.php');
return DependencyFactory::fromEntityManager(
$migrationsConfig,
new ExistingEntityManager($entityManager)
);
И файл настроек самих миграций migrations.php — куда складывать сгенерированные классы:
<?php
// migrations.php
return [
'migrations_paths' => [
'DoctrineMigrations' => __DIR__ . '/migrations',
],
];
Точные имена классов конфигурации привязаны к версии doctrine/migrations. Если у вас другая мажорная версия и импорты не резолвятся — сверьтесь с разделом Configuration в документации Migrations, структура файлов там менялась между 2.x и 3.x.
Шаг 5. Генерация миграции
Теперь ключевой момент: Doctrine сравнивает метаданные ваших сущностей с текущим состоянием базы и генерирует SQL разницы. Запустите:
vendor/bin/doctrine-migrations diff
Команда создаст файл вида migrations/VersionYYYYMMDDHHMMSS.php. Откройте его — внутри метода up() должен быть CREATE TABLE products с колонками id, name, price. Если тела up() пустое и в консоли написано No changes detected — значит, Doctrine не нашёл сущность. Проверьте путь в ORMSetup::createAttributeMetadataConfiguration и namespace класса.
Шаг 6. Применение миграции
vendor/bin/doctrine-migrations migrate
Инструмент спросит подтверждение и выполнит SQL. После этого в базе появятся две таблицы: ваша products и служебная doctrine_migration_versions — в ней Doctrine хранит список уже применённых миграций, чтобы не выполнять их повторно. Проверьте в клиенте базы: SHOW TABLES; для MySQL. Обе таблицы на месте — вы прошли путь целиком.
Если не получилось
- SQLSTATE[HY000] [1045] Access denied for user — неверный пользователь или пароль в
bootstrap.phpиmigrations-db.php. Правьте оба файла: они не связаны между собой. - Unknown database 'shop' — база не создана. Doctrine таблицы делает, а базу нет: создайте её вручную командой
CREATE DATABASE shop;. - could not find driver — не установлено расширение PDO для вашей СУБД. Поставьте
pdo_mysqlилиpdo_pgsqlи перезапустите PHP-CLI. - No changes detected при
diff— Doctrine не видит сущность. Частая причина: namespace класса не совпадает с путём или путь в конфигурации указывает не на ту папку. - Cannot load Doctrine\Migrations... при запуске
cli-config.php— версияdoctrine/migrationsотличается, имена классов конфигурации другие. Сверьте с документацией установленной версии.
Что делать дальше
Первую таблицу вы создали через миграцию — теперь правило: каждое изменение схемы идёт через diff и migrate, а не руками в базе. Так схема остаётся воспроизводимой на всех окружениях и в CI. Дальше стоит разобрать связи между сущностями (OneToMany, ManyToOne) — именно ради них берут ORM, а не голый SQL. И посмотрите репозитории Doctrine: запросы к базе оформляются через них, а не прямыми SQL-строками.
Проверка грамотности: Орфография, пунктуация, стиль — с объяснением каждой правки
Спросить за 10 ₽Источники: Doctrine ORM — Getting Started, Doctrine Migrations — Documentation
Частые вопросы
У меня Doctrine ORM 2.x, а не 3.x — код подойдёт?
Обязательно ли вручную создавать базу данных перед миграцией?
Чем diff отличается от команды, которая создаёт пустую миграцию?
Можно ли откатить применённую миграцию?
Почему появилась таблица doctrine_migration_versions и можно ли её удалить?
Хранить пароль базы прямо в bootstrap.php — это нормально?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.