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

Ответ

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();

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

Ответ 18+ 🔞

Давай разберём эту вашу Doctrine ORM, чтоб она не казалась какой-то хитрой жопой с ушами. Это же просто набор паттернов, которые кто-то умный придумал, чтобы нам, разработчикам, жизнь мёдом не казалась. Ага, щас.

На чём она там стоит:

  1. Data Mapper (Разделитель всего и вся). Это, бля, основа основ. Суть проще пареной репы: твой красивый PHP-объект (сущность) живёт своей жизнью и нихуя не знает про базу данных. Он просто класс с полями. А за то, чтобы его данные волшебным образом оказались в таблице products, отвечает отдельная сущность — EntityManager. Представь, ты написал класс Product, навешал на него атрибуты #[ORM...], а потом просто говоришь менеджеру: «Мужик, запомни этот объект» (persist()), а потом «А теперь, сука, всё, что запомнил, запиши в базу» (flush()). И он записывает. Красота, ёпта. Твой код бизнес-логики не перемазан SQL-ом, как котлета в соусе.

    // Вот твой объект. Чистый, как слеза младенца. Никаких `mysqli_query` внутри.
    #[ORMEntity]
    class Product
    {
        #[ORMId, ORMGeneratedValue, ORMColumn]
        private ?int $id = null;
        #[ORMColumn]
        private string $name;
        // ... геттеры и сеттеры
    }
    // А это уже магия менеджера. Он — тот самый маппер.
    $entityManager->persist($product);
    $entityManager->flush(); // И пошло-поехало в базу
  2. Unit of Work (Единица работы, ага). Это, если по-простому, внутренняя тетрадка EntityManager-а. Пока ты работаешь с сущностями (создаёшь, меняешь, удаляешь), он всё это тихонько в свой блокнотик записывает. А когда ты вызываешь flush(), он открывает этот блокнот, смотрит: «Так-с, этот продукт новый — надо INSERT, этот поменял имя — UPDATE, а этот вообще удалили — DELETE». И генерирует ровно столько запросов, сколько нужно, ни хуя лишнего. Умно, чёрт возьми.

  3. Identity Map (Карта „это уже было“). Чтобы не вышло, как в том анекдоте, когда левая рука не знает, что делает правая. EntityManager следит, чтобы если ты дважды запросил пользователя с id = 1, тебе вернулся один и тот же объект в памяти. Не две копии, которые потом будут конфликтовать, кто из них правильный, а один. Это ж элементарно, Ватсон! И запрос в базу за ним делается только один раз, а не два. Экономия, блядь.

    $user1 = $entityManager->find(User::class, 1); // Запрос в БД
    $user2 = $entityManager->find(User::class, 1); // Запроса НЕТ! Возвращается тот же $user1
    // $user1 === $user2 // true, ёба!
  4. Repository (Умный доступ к данным). Это такая красивая обёрточка для работы с коллекциями сущностей. Вместо того чтобы писать SELECT * FROM users WHERE status = 'active' ORDER BY created_at DESC, ты получаешь репозиторий для User и говоришь findBy(['status' => 'active'], ['createdAt' => 'DESC']). А если запрос сложный — пишешь свой метод на DQL (это типа SQL, но для объектов). Удобно, чётко, всё в одном месте.

  5. Proxy / Lazy Loading (Хитрый ленивый загрузчик). Вот это, пожалуй, одна из самых прикольных фишек. Допустим, у тебя есть Заказ и в нём куча Позиций. Когда ты получаешь заказ, Doctrine тебе сразу все позиции из базы не тащит — овердохуища же данных может быть. Вместо этого он подсовывает тебе объект-пустышку (прокси). И только когда ты в коде реально обратишься к этим позициям ($order->getItems()->toArray()), прокси скажет: «Ага, щас!» — и выполнит запрос, загрузив данные. Лениво, но эффективно. Главное, потом не охуеть от количества запросов в цикле, если не настроить join — но это уже другая история.

    $order = $entityManager->find(Order::class, 123); // Запрос только для заказа
    // ... проходит куча кода ...
    // Ага, вот теперь понадобились товары!
    $items = $order->getItems()->toArray(); // БАЦ! Выполняется второй запрос к БД

Вот и вся магия, без всяких гомосеков и мартышлюшек. Эти паттерны вместе создают прослойку, которая позволяет работать с реляционной базой почти как с обычными объектами. Не идеально, иногда приходится ругаться матом на производительность, но, ебать копать, лучше этого пока ничего не придумали.