Ответ
Doctrine ORM для PHP является ярким примером применения нескольких ключевых паттернов предметно-ориентированного проектирования (DDD) и работы с данными.
Основные паттерны в Doctrine:
-
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(); -
Unit of Work: Паттерн, реализуемый
EntityManager. Он отслеживает все изменения, сделанные с управляемыми сущностями в течение бизнес-транзакции. При вызовеflush()вычисляется минимальный набор SQL-запросов (INSERT, UPDATE, DELETE) для синхронизации состояния БД. -
Identity Map:
EntityManagerгарантирует, что в рамках одного запроса загрузка сущности по идентификатору всегда возвращает один и тот же экземпляр объекта. Это предотвращает конфликты и избыточные запросы.$user1 = $entityManager->find(User::class, 1); $user2 = $entityManager->find(User::class, 1); // $user1 и $user2 — это один и тот же объект в памяти ($user1 === $user2 // true) -
Repository: Предоставляет абстракцию для коллекции сущностей. В Doctrine репозитории предлагают методы типа
find(),findBy(),findOneBy(), а также позволяют создавать собственные, более сложные DQL-запросы. -
Proxy (Lazy Loading): Для ассоциаций (например,
@OneToMany) Doctrine генерирует классы-прокси. Фактическая загрузка связанных данных из БД происходит только при первом обращении к ним, что экономит ресурсы.// Загружается только сущность Order $order = $entityManager->find(Order::class, 123); // Следующая строка triggers lazy load: выполняется запрос к БД для items $items = $order->getItems()->toArray();
Эти паттерны вместе обеспечивают эффективное, предсказуемое и удобное в разработке взаимодействие с реляционной БД на высоком уровне абстракции.
Ответ 18+ 🔞
Давай разберём эту вашу Doctrine ORM, чтоб она не казалась какой-то хитрой жопой с ушами. Это же просто набор паттернов, которые кто-то умный придумал, чтобы нам, разработчикам, жизнь мёдом не казалась. Ага, щас.
На чём она там стоит:
-
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(); // И пошло-поехало в базу -
Unit of Work (Единица работы, ага). Это, если по-простому, внутренняя тетрадка
EntityManager-а. Пока ты работаешь с сущностями (создаёшь, меняешь, удаляешь), он всё это тихонько в свой блокнотик записывает. А когда ты вызываешьflush(), он открывает этот блокнот, смотрит: «Так-с, этот продукт новый — надоINSERT, этот поменял имя —UPDATE, а этот вообще удалили —DELETE». И генерирует ровно столько запросов, сколько нужно, ни хуя лишнего. Умно, чёрт возьми. -
Identity Map (Карта „это уже было“). Чтобы не вышло, как в том анекдоте, когда левая рука не знает, что делает правая.
EntityManagerследит, чтобы если ты дважды запросил пользователя сid = 1, тебе вернулся один и тот же объект в памяти. Не две копии, которые потом будут конфликтовать, кто из них правильный, а один. Это ж элементарно, Ватсон! И запрос в базу за ним делается только один раз, а не два. Экономия, блядь.$user1 = $entityManager->find(User::class, 1); // Запрос в БД $user2 = $entityManager->find(User::class, 1); // Запроса НЕТ! Возвращается тот же $user1 // $user1 === $user2 // true, ёба! -
Repository (Умный доступ к данным). Это такая красивая обёрточка для работы с коллекциями сущностей. Вместо того чтобы писать
SELECT * FROM users WHERE status = 'active' ORDER BY created_at DESC, ты получаешь репозиторий дляUserи говоришьfindBy(['status' => 'active'], ['createdAt' => 'DESC']). А если запрос сложный — пишешь свой метод на DQL (это типа SQL, но для объектов). Удобно, чётко, всё в одном месте. -
Proxy / Lazy Loading (Хитрый ленивый загрузчик). Вот это, пожалуй, одна из самых прикольных фишек. Допустим, у тебя есть
Закази в нём кучаПозиций. Когда ты получаешь заказ, Doctrine тебе сразу все позиции из базы не тащит — овердохуища же данных может быть. Вместо этого он подсовывает тебе объект-пустышку (прокси). И только когда ты в коде реально обратишься к этим позициям ($order->getItems()->toArray()), прокси скажет: «Ага, щас!» — и выполнит запрос, загрузив данные. Лениво, но эффективно. Главное, потом не охуеть от количества запросов в цикле, если не настроитьjoin— но это уже другая история.$order = $entityManager->find(Order::class, 123); // Запрос только для заказа // ... проходит куча кода ... // Ага, вот теперь понадобились товары! $items = $order->getItems()->toArray(); // БАЦ! Выполняется второй запрос к БД
Вот и вся магия, без всяких гомосеков и мартышлюшек. Эти паттерны вместе создают прослойку, которая позволяет работать с реляционной базой почти как с обычными объектами. Не идеально, иногда приходится ругаться матом на производительность, но, ебать копать, лучше этого пока ничего не придумали.