Doctrine ORM для PHP: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Doctrine ORM для PHP: полное руководство с примерами

1. Что это и зачем нужна?

Doctrine ORM (Object-Relational Mapper) — это мощная библиотека для PHP, реализующая паттерн Data Mapper. В отличие от Active Record (используемого в Laravel Eloquent), Doctrine полностью отделяет бизнес-логику (сущности) от логики работы с базой данных. Сущности — это обычные PHP-объекты (Plain Old PHP Objects, POPO), которые не знают о существовании базы данных. Вся работа с БД осуществляется через центральный репозиторий — EntityManager.

Зачем это нужно? Doctrine решает фундаментальную проблему несоответствия между объектно-ориентированной моделью данных и реляционной моделью базы данных (Object-Relational Impedance Mismatch). Вы работаете с объектами и коллекциями, а Doctrine автоматически транслирует ваши действия в SQL-запросы. Это позволяет писать более чистый, тестируемый и поддерживаемый код, особенно в крупных enterprise-проектах.

Ключевые преимущества: полный контроль над SQL (через DQL — Doctrine Query Language), ленивая загрузка (Lazy Loading), гибкая система событий (Lifecycle Events), поддержка наследования (Mapped Superclass, Single Table Inheritance, Joined Table Inheritance), встроенный Query Builder и гидраторы (Hydration Modes). Doctrine — стандарт де-факто для сложных PHP-приложений (Symfony, Drupal, Magento).

2. Установка

Установка производится через Composer (аналог pip для PHP). Для базового использования достаточно двух пакетов:

composer require doctrine/orm
composer require doctrine/dbal
composer require symfony/cache  # Рекомендуется для кэширования метаданных

Для работы с аннотациями (устаревший, но популярный способ) требуется дополнительный пакет:

composer require doctrine/annotations

Для работы с YAML или XML конфигурацией:

composer require symfony/yaml  # Для YAML

После установки убедитесь, что автозагрузчик Composer подключен в вашем проекте:

require_once __DIR__ . '/vendor/autoload.php';

3. Быстрый старт — минимальный рабочий пример

Создадим простую сущность User и сохраним её в базу данных SQLite.

Шаг 1: Конфигурация EntityManager (bootstrap.php)

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
use Doctrine\DBAL\DriverManager;

require_once __DIR__ . '/vendor/autoload.php';

// Настройка путей к сущностям и режима разработки
$paths = [__DIR__ . '/src/Entity'];
$isDevMode = true;

// Создаем конфигурацию ORM
$config = ORMSetup::createAttributeMetadataConfiguration($paths, $isDevMode);

// Настройка подключения к БД (SQLite)
$connectionParams = [
    'driver' => 'pdo_sqlite',
    'path' => __DIR__ . '/data/database.sqlite',
];

$connection = DriverManager::getConnection($connectionParams, $config);

// Создаем EntityManager
$entityManager = new EntityManager($connection, $config);

Шаг 2: Создание сущности (src/Entity/User.php)

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private int|null $id = null;

    #[ORM\Column(type: 'string', length: 100)]
    private string $name;

    #[ORM\Column(type: 'string', length: 255, unique: true)]
    private string $email;

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

    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;
        return $this;
    }

    public function getEmail(): string
    {
        return $this->email;
    }

    public function setEmail(string $email): self
    {
        $this->email = $email;
        return $this;
    }
}

Шаг 3: Создание схемы БД и сохранение данных (create_user.php)

require_once __DIR__ . '/bootstrap.php';

use App\Entity\User;

// Создание схемы таблиц
$schemaTool = new \Doctrine\ORM\Tools\SchemaTool($entityManager);
$classes = $entityManager->getMetadataFactory()->getAllMetadata();
$schemaTool->createSchema($classes);

// Создание нового пользователя
$user = new User();
$user->setName('Иван Петров');
$user->setEmail('ivan@example.com');

// Сохранение в БД
$entityManager->persist($user);
$entityManager->flush();

echo "Пользователь создан с ID: " . $user->getId() . PHP_EOL;

Запустите скрипт: php create_user.php. В папке data появится файл database.sqlite с таблицей users и одной записью.

4. Основные методы/функции/классы

4.1. EntityManager::persist()

Сигнатура: void persist(object $entity)

Описание: Сообщает EntityManager, что объект должен быть сохранен в БД при следующем вызове flush(). Объект переводится в состояние MANAGED.

$user = new User();
$user->setName('Анна');
$entityManager->persist($user);
// Объект еще не сохранен в БД

4.2. EntityManager::flush()

Сигнатура: void flush()

Описание: Выполняет все накопленные операции (INSERT, UPDATE, DELETE) в базе данных. Это ключевой метод, который синхронизирует состояние объектов с БД.

$entityManager->persist($user);
$entityManager->flush(); // Теперь данные записаны в БД

4.3. EntityManager::find()

Сигнатура: object|null find(string $className, mixed $id)

Описание: Находит сущность по её первичному ключу. Возвращает объект или null, если запись не найдена.

$user = $entityManager->find(User::class, 1);
if ($user) {
    echo $user->getName(); // Иван Петров
}

4.4. EntityManager::getRepository()

Сигнатура: EntityRepository getRepository(string $entityName)

Описание: Возвращает репозиторий для работы с сущностями заданного класса. Репозиторий предоставляет методы для поиска (findBy, findOneBy, findAll) и кастомные запросы.

$userRepository = $entityManager->getRepository(User::class);
$users = $userRepository->findBy(['name' => 'Анна']); // Все Анны
$user = $userRepository->findOneBy(['email' => 'ivan@example.com']); // Один пользователь

4.5. QueryBuilder

Сигнатура: QueryBuilder createQueryBuilder()

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

$queryBuilder = $entityManager->createQueryBuilder();
$query = $queryBuilder
    ->select('u')
    ->from(User::class, 'u')
    ->where('u.email LIKE :email')
    ->setParameter('email', '%@example.com')
    ->orderBy('u.name', 'ASC')
    ->setMaxResults(10)
    ->getQuery();

$users = $query->getResult();

4.6. EntityManager::remove()

Сигнатура: void remove(object $entity)

Описание: Помечает сущность для удаления. При следующем вызове flush() запись будет удалена из БД.

$user = $entityManager->find(User::class, 1);
$entityManager->remove($user);
$entityManager->flush(); // Пользователь удален

4.7. EntityManager::clear()

Сигнатура: void clear(string|null $entityName = null)

Описание: Очищает Unit of Work (кэш объектов). Все MANAGED объекты становятся DETACHED. Полезно для освобождения памяти при обработке больших объемов данных.

// Обработка 10000 записей
for ($i = 0; $i < 10000; $i++) {
    $user = new User();
    $user->setName("User $i");
    $entityManager->persist($user);
    
    if ($i % 500 === 0) {
        $entityManager->flush();
        $entityManager->clear(); // Очищаем память каждые 500 записей
    }
}
$entityManager->flush();

4.8. Query::getResult() и Query::getSingleResult()

Сигнатура: array getResult(int $hydrationMode = Query::HYDRATE_OBJECT)

Описание: Выполняет запрос и возвращает результаты. getResult() возвращает массив, getSingleResult() — один объект (выбрасывает исключение, если результатов больше одного).

$query = $entityManager->createQuery('SELECT u FROM App\Entity\User u WHERE u.id > :id');
$query->setParameter('id', 5);

// Получить как объекты
$users = $query->getResult(); // Массив объектов User

// Получить как массивы (быстрее, меньше памяти)
$usersData = $query->getResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);

// Получить один объект
$user = $query->setMaxResults(1)->getSingleResult();

4.9. EntityManager::createQuery()

Сигнатура: Query createQuery(string $dql = '')

Описание: Создает объект Query из строки DQL (Doctrine Query Language). DQL похож на SQL, но оперирует объектами и их свойствами.

$dql = "SELECT u FROM App\Entity\User u WHERE u.email = :email";
$query = $entityManager->createQuery($dql);
$query->setParameter('email', 'ivan@example.com');
$user = $query->getOneOrNullResult();

4.10. ClassMetadata и аннотации/атрибуты

Сигнатура: ClassMetadata getClassMetadata(string $className)

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

$metadata = $entityManager->getClassMetadata(User::class);
echo "Таблица: " . $metadata->getTableName(); // users
print_r($metadata->getFieldNames()); // ['id', 'name', 'email']
print_r($metadata->getAssociationNames()); // Связи (если есть)

5. Реальный практический пример (100-200 строк кода)

Создадим систему управления заказами с двумя сущностями: Customer и Order (связь OneToMany).

Сущность Customer (src/Entity/Customer.php):

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'customers')]
class Customer
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private int|null $id = null;

    #[ORM\Column(type: 'string', length: 150)]
    private string $name;

    #[ORM\Column(type: 'string', length: 255)]
    private string $email;

    #[ORM\OneToMany(mappedBy: 'customer', targetEntity: Order::class, cascade: ['persist', 'remove'])]
    private Collection $orders;

    public function __construct()
    {
        $this->orders = new ArrayCollection();
    }

    // Геттеры и сеттеры
    public function getId(): ?int { return $this->id; }
    public function getName(): string { return $this->name; }
    public function setName(string $name): self { $this->name = $name; return $this; }
    public function getEmail(): string { return $this->email; }
    public function setEmail(string $email): self { $this->email = $email; return $this; }
    public function getOrders(): Collection { return $this->orders; }

    public function addOrder(Order $order): self
    {
        if (!$this->orders->contains($order)) {
            $this->orders[] = $order;
            $order->setCustomer($this);
        }
        return $this;
    }

    public function removeOrder(Order $order): self
    {
        if ($this->orders->removeElement($order)) {
            if ($order->getCustomer() === $this) {
                $order->setCustomer(null);
            }
        }
        return $this;
    }
}

Сущность Order (src/Entity/Order.php):

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'orders')]
class Order
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private int|null $id = null;

    #[ORM\Column(type: 'string', length: 50)]
    private string $status = 'new';

    #[ORM\Column(type: 'decimal', precision: 10, scale: 2)]
    private float $total = 0.0;

    #[ORM\ManyToOne(targetEntity: Customer::class, inversedBy: 'orders')]
    #[ORM\JoinColumn(nullable: false)]
    private Customer|null $customer = null;

    // Геттеры и сеттеры                    

Рекомендаций