[Из песочницы] Почему Doctrine ORM плохо подходит для PHP
Статья является переводом заметки Why Doctrine ORM is not suited for PHP от Lucas Corbeaux.Я знаю, что заголовок этой статьи похож на троллинг. Но это не так, это просто констатация факта. Я не пытаюсь сказать, что Doctrine — это плохая библиотека или что её не нужно использовать. Я просто говорю, что она плохо подходит для PHP, и если не принимать этот момент во внимание и использовать её неправильно, можно столкнуться с серьёзными проблемами.
Doctrine вдохновлена Hibernate ORMВернёмся назад в 2000-е. Java очень популярен и одна из самых часто используемых Java-библиотек — это ORM Hibernate. Она поставлялась вместе с собственным языком запросов HQL и была горячо любима Java-сообществом. Hibernate помогала Java-объектам и реляционным таблицам сосуществовать вместе.Doctine был вдохновлён концептами Hibernate и хотел привнести их в мир PHP.
Различия между Java и PHP Но PHP — это не Java, и из этого можно сделать один очень важный вывод. Java приложение живёт намного дольше, чем PHP запрос.ORM должен учитывать целостность данных между всеми пользователями. В течение продолжительного времени каждое изменение в базе данных должно быть отражено для всех объектов. Это одна из причин, по которым ORM настолько сложны.
И именно поэтому ORM-паттерны в основном не нужны PHP. Поскольку HTTP протокол является протоколом «без сохранения состояния», вам не нужно поддерживать согласованность данных между между всеми вызовами.
Проблема сессий Конечно же, вы можете сказать мне, что это неправда. Можно использовать сессии, чтобы хранить объекты между запросами, и тогда нужен способ, чтобы поддерживать их целостность. Это разумный аргумент. Вот только сериализация сущностей в Doctrine довольно каверзна и может привести с серьёзным проблемам.Identity Map бесполезна в окружении «без сохранения состояния» Identity Map — это часть Doctrine, которая поддерживает уникальность сущностей. Если вы, например, дважды запросите сущность с ID 4, то вы оба раза получите тот же самый объект.На первый взгляд выглядит как отличная идея. Но в чём суть изолированного выполнения?
Если ваш код хорошо структурирован, то вам и не понадобится дважды запрашивать одну и ту же сущность. Вместо этого вы воспользуетесь Dependency Injection; Если вы измените данные, то это потому, что вы получили POST запрос. При получении POST запроса хорошей практикой считается сразу выполнить редирект. Нет никакой необходимости «обновлять» объекты. Мне кажется, что доктриновская Identity Map полезна только в случае плохого дизайна. Или в очень редком и особенном случае.
UnitOfWork слишком переусложнён UnitOfWork — это одна из основных частей Doctrine ORM. Я не говорю, что она бесполезная, но она слишком переусложнена. Я уже говорил о сессиях и проблеме сериализации. Управление сущностями — это вещь комплексная, и со сложностью реализации я смириться могу. Этот момент довольно-таки трудно реализовать.Но с чем я не могу смириться, так это то, что большая часть сложностей возникла из-за «ленивой» загрузки и политик отслеживания изменений.
«Ленивая» загрузка бессмысленна В окружении «без сохранения состояния», «ленивая» загрузка является плохой практикой. Мне нечего к этому добавить. Возможно, можно найти случаи, когда это немного увеличивало производительность, но это случается очень редко. Так почему же тогда это один из центральных концептов в Doctrine ORM? Почти каждый раз, когда я разговариваю с командой, использующую Doctrine, они признаются, что у них были проблемы из-за злоупотребления автозагрузкой. И это происходит, даже с опытными разработчиками.
Для одного из своих клиентов, я даже специально писал логгер, чтобы обнаруживать и удалять использования «ленивой» загрузки. Это просто вопиющий пример бесполезности.
Политики отслеживания изменений Зачем нам нужна настолько сложная система, для обеспечивания целостности данных? Мы же в окружении «без сохранения состояния»! Если у нашего приложения хорошая архитектура, то данные не меняются в случайных местах. За исключением редких случаев (логирование, обновление времени последнего коннекта и проч.), нам просто нужно изменить данные по POST запросу. После чего мы незамедлительно редиректим пользователя на другую страницу.Так зачем же нам нужна такая сложная система? Чтобы спрятать плохо спроектированное приложение?
В EntityManager слишком много магии Глобальный EntityManager ведёт себя как паттерн Фасад, который работает с другими ORM-объектами. Это очень мощный инструмент, который может быть вызван в любом месте кода.Однако, я свято верю в то, что в хорошо спроектированном приложении, EntityManager должен использоваться только в трёх случаях:
При загрузке для конфигурации В ваших «фабриках», Service Manager или Dependency Injector, чтобы инициализировать необходимые объекты. В некоторых репозиториях, если нужно создать SQL запрос В других местах его использовать не нужно.
Всё же интересная и мощная библиотека Для ясности, повторю во второй раз: я не говорю, что Doctine ORM бесполезна или что вы не должны её использовать. Меня больше всего беспокоит то, что библиотека навязывает вредные привычки.Entity Map позволяет разработчикам быть неаккуратными с инъекцией зависимостей и, соотвественно, экземплярами сущностей. «Ленивая» загрузка работает слишком магически и прячет проблемы производительности, пока не станет слишком поздно. Это особенно касается разработчиков с небольшим количеством опыта. Но опытные разработчики иногда тоже попадают в эту западню, ведь порой так заманчиво не утруждать себя использованием fetch-join. EntityManager позволяет делать что угодно и где угодно. Удобно, но очень далеко от хороших практик. Что дальше? У автора оригинальной статьи есть материал для ещё нескольких статей о Doctrine. Например, о ODM расширении или о генераторах. Кроме того он принимает заявки в комментариях.