Какие паттерны проектирования используются в ORM Doctrine?

«Какие паттерны проектирования используются в ORM Doctrine?» — вопрос из категории Паттерны, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Doctrine ORM для PHP является ярким примером применения нескольких ключевых паттернов предметно-ориентированного проектирования (DDD) и работы с данными.

Основные паттерны в Doctrine:

  1. Data Mapper: Это фундаментальный паттерн Doctrine. Он полностью отделяет бизнес-объекты (сущности) от логики их сохранения в БД. Класс-сущность ничего не знает о том, как он persists.

    // Сущность — чистый PHP-объект
    #[ORMEntity]
    class Product
    {
        #[ORMId, ORMGeneratedValue, ORMColumn]
        private ?int $id = null;
        #[ORMColumn]
        private string $name;
        // ... геттеры и сеттеры
    }
    // Сохранением управляет EntityManager (часть Data Mapper)
    $entityManager->persist($product);
    $entityManager->flush();
  2. Unit of Work: Паттерн, реализуемый EntityManager. Он отслеживает все изменения, сделанные с управляемыми сущностями в течение бизнес-транзакции. При вызове flush() вычисляется минимальный набор SQL-запросов (INSERT, UPDATE, DELETE) для синхронизации состояния БД.

  3. Identity Map: EntityManager гарантирует, что в рамках одного запроса загрузка сущности по идентификатору всегда возвращает один и тот же экземпляр объекта. Это предотвращает конфликты и избыточные запросы.

    $user1 = $entityManager->find(User::class, 1);
    $user2 = $entityManager->find(User::class, 1);
    // $user1 и $user2 — это один и тот же объект в памяти ($user1 === $user2 // true)
  4. Repository: Предоставляет абстракцию для коллекции сущностей. В Doctrine репозитории предлагают методы типа find(), findBy(), findOneBy(), а также позволяют создавать собственные, более сложные DQL-запросы.

  5. Proxy (Lazy Loading): Для ассоциаций (например, @OneToMany) Doctrine генерирует классы-прокси. Фактическая загрузка связанных данных из БД происходит только при первом обращении к ним, что экономит ресурсы.

    // Загружается только сущность Order
    $order = $entityManager->find(Order::class, 123);
    // Следующая строка triggers lazy load: выполняется запрос к БД для items
    $items = $order->getItems()->toArray();

Эти паттерны вместе обеспечивают эффективное, предсказуемое и удобное в разработке взаимодействие с реляционной БД на высоком уровне абстракции.