Какой паттерн реализует Doctrine ORM
Что проверяют
Знаете ли вы, что Doctrine ORM реализует Data Mapper, и понимаете ли, чем он отличается от Active Record в Eloquent. Интервьюер проверяет, назовёте ли вы Unit of Work, потому что без него не объяснить поведение flush(). Следом спрашивают про ленивую загрузку и N+1.
Как отвечать
Главный паттерн Data Mapper: сущность это обычный PHP-объект без метода save() и без знания о базе, маппинг описывается атрибутами #[ORM\Entity], #[ORM\Column] или XML. Переносом данных между объектом и таблицей занимается EntityManager. Вокруг него ещё несколько паттернов из каталога Фаулера:
- Unit of Work:
persist()только ставит новый объект на учёт, реальныеINSERT,UPDATEиDELETEуходят приflush()одной транзакцией. Изменения уже управляемых сущностей отслеживаются автоматически, повторныйpersist()для них не нужен; - Identity Map: в рамках одного
EntityManagerзапись с данным id существует в одном экземпляре, повторныйfind()вернёт тот же объект без запроса; - Lazy Load через Proxy: связанные сущности подгружаются при первом обращении через прокси-объекты;
- Repository:
EntityRepositoryи свои классы репозиториев для запросов; - Query Object: DQL и
QueryBuilderописывают запросы в терминах сущностей, а не таблиц.
Сравнение уложите в две фразы: Active Record проще и быстрее для CRUD, но смешивает домен с хранением. Data Mapper оставляет доменную модель чистой и тестируемой ценой большего числа понятий.
Где спотыкаются
Говорят, что Doctrine поддерживает и Active Record. Doctrine ORM 2 и 3 это чистый Data Mapper, Active Record был в давно заброшенной Doctrine 1, а сегодня в PHP его представляют Eloquent и Yii.
Ожидают, что persist() пишет в базу, и удивляются, почему без flush() ничего не сохранилось. Обратная ошибка: flush() на каждую строку в цикле, и импорт превращается в тысячи транзакций.
Обходят связи в цикле и получают N+1 через прокси. Лечится fetch join в DQL (SELECT u, a FROM User u JOIN u.addresses a) или fetch: 'EAGER' там, где это оправдано.