Друзья, вновь пришло время авторской колонки корпоративного блога PG Day’17. Предлагаем вашему вниманию сравнительный анализ работы с PostgreSQL из популярных ORM от varanio.  ORM (Object-Relational Mapping), по идее, должен избавить нас от написания SQL запросов и, в идеале, вообще абстрагировать от базы данных (от способа хранения данных), чтобы мы могли работать с классами, в той или иной степени выражающими объекты бизнес-логики, не задаваясь вопросом, в каких таблицах всё это по факту лежит. Посмотрим, насколько это удается современным библиотекам на PHP. Давайте рассмотрим несколько типичных кейсов и сравним ORM с голым SQL, написанным вручную. Для примера возьмем две таблицы: книги и авторы книг, отношение многие-ко-многим (у книг может быть много авторов, у авторов может быть много книг). Т.е. в базе это будут books, authors и связующая таблица author_book: Рассмотрим несколько кейсов использования. Добавим авторов и книг. Ну, тут всё просто и прямолинейно: Многовато писанины. Можно не использовать прям совсем голый PDO, а взять что-нибудь чуть полаконичнее, какую-нибудь легкую обертку. Но в любом случае надо писать запросы вручную и знать синтаксис SQL. В Laravel используется ORM под названием Eloquent. Eloquent — это, по сути, ActiveRecord, т.е. отображение таблиц на некие соответствующие им классы ("модели"), причем модель сама умеет себя сохранять. Итак, делаем две модели. По умолчанию даже имена таблиц нигде указывать не надо, если они называются как классы. Надо указать $timestamps = false, чтобы не сохраняло автоматически время обновления модели. Как видно, мы запросто описали отношение many-to-many буквально парой строк кода. Создание записей в базе и связь между ними делается достаточно просто: Или списком: Так, конечно, поприятнее, чем возиться с SQL, и даже запись в связочную таблицу делается очень легко. В доктрине используется подход DataMapper. По уверениям документации, объекты бизнес-логики отделены от способа сохранения. Здесь объекты получаются из Репозитория (Repository), т.е. сущность не знает как себя получить, это знает только Repository, а для сохранения потребуется EntityManager. Сгенерировать классы из существующих таблиц можно одним движением:  Первая команда создаст yml-файлы для сущностей, описывающие типы полей в базе, взаимосвязь объектов (например, many-to-many) и т.д. Вторая команда создаст классы сущностей.  Прямо скажем, yml получились немаленькие, и они набиты подробностями о структуре таблиц и их связях. Вообще, можно обойтись и без yml, всё делая в аннотациях классов. Но когда классы сущностей совершенно отделены от базы, это больше соответствует концепции DDD. Зато сами классы-сущности у нас получились совершенно простые, т.е. POJO (plain old php object): Создаем объекты и сохраняем. Примерно так: В целом, использование ORM для простых случаев создания записей в таблицах является более предпочтительным способом, чем чистый SQL. Методы setName и т.д. в коде читаются лучше, чем SQL-запрос. Нет жесткой зависимости от БД. Обновление какого-то поля в целом тоже вполне можно делать через ORM, не вдаваясь в детали SQL. Для тестов создадим такие записи в таблице: Если брать голый SQL для вывода списка книг с авторами, то это будет примерно так (допустим, авторов хотим получить в виде json): Результат: Сделаем сначала втупую из мануалов а-ля "Getting Started": Код получился гораздо проще, чем голый SQL. Все просто и понятно, works like magic. Только при детальном рассмотрении магия там достаточно фиговая. Eloquent делает аж 4 запроса: Для маленького ненагруженного сайта сойдет и так. Но если сайт нагружен и таблицы содержат много строк и данных, то это провал. Во-первых, конструкции select * и select authors.*. За такое сразу партбилет на стол. Если книги будут "жирными" ("Война и Мир" или "Британская энциклопедия"), то ни к чему тянуть сразу их текст, когда нужен только список названий. К тому же, со временем в таблицах количество полей обычно все нарастает и нарастает, т.е. такое приложение будет работать всё медленнее и медленнее, жрать всё больше и больше памяти. Я уж не говорю о том, что количество запросов authors.* равно количеству книг. Что тут можно предпринять? Во-первых, можно указать, какие поля берем из книги, т.е (['id', 'name']). Ну, и использовать with() для т.н. "eager loading". Итого: Стало немного получше, но всё равно далеко от идеала: Тут две проблемы: authors идут всё равно со звездочкой. Кроме того, появился оператор in() с перечислением всех id, который нормально работает при маленьком количестве книг, но для большого списка это будет работать очень медленно, по крайней мере в PostgreSQL. Хотя, конечно, быстрее, чем по запросу на каждый. И с этим уже, похоже, ничего не сделать, по крайней мере я ничего не нашел. Точнее, помимо ORM есть еще Query Builder: Но это, повторяю, не ORM. Это тот же SQL, только вместо пробелов стрелочки и куча методов, которые надо знать дополнительно. Для начала тоже попробуем по-простому: Код первой попытки почти такой же как в Laravel. SQL-запросы, в общем, тоже: Т.е. перечислены все поля, и по запросу на каждую книгу с перечислением, опять же, всех полей. У стандартных методов типа findAll и т.д., похоже, нет способа указать, что мне надо только такие-то поля и сразу приджойнить такие-то таблицы. Но, зато в Доктрине есть SQL-подобный синтаксис DQL, абстрагированный от конкретной СУБД, которым можно воспользоваться. Он оперирует не таблицами, а сущностями. Ну да, получилось типа того, что надо, один запрос, с одним полем: На мой взгляд, простой SQL выглядит проще и стандартнее. Кроме того, в ORM-подходах мы не смогли полностью сферически абстрагироваться от базы данных, нам пришлось подстроиться под реальный мир. DQL в принципе сойдет на замену SQL, и он не особо привязан к СУБД, но это еще один странноватый синтаксис, который надо учить отдельно. Допустим, стоит задача обновить двум последним авторам имя на "Жорж".  Тут всё просто, запрос с подзапросом. Сначала я попробовал сделать так: Это было бы здорово и красиво, однако не сработало. Точнее сработало, но заменило записи всем авторам, а не только двум. Тогда, покурив мануал и SO, удалось родить такую конструкцию: Это работает хорошо, хоть и не особо читабельно. Да и query builder опять какой-то подъехал. Сразу скажу, что выразить через DQL мне этот запрос вообще не удалось, с вложенными подзапросами там всё плохо. Есть, конечно, query builder, но получалось что-то совсем зубодробительное, и я бросил эту затею. ORM должен помогать экономить время, а не наоборот. Надеюсь, опытные симфонисты в коментах подскажут какой-нибудь легкий и изящный способ сделать update с подзапросом. Несмотря на привлекательную идею использовать классы бизнес-логики и не вдаваться в детали реализации хранения данных, сделать это удается далеко не всегда.  Как всегда обычно и бывает, истина где-то посередине. Для простых CRUD-операций ORM вполне может сэкономить время разработки и улучшить читабельность кода. Однако шаг вправо, шаг влево — и гораздо удобнее пользоваться нативным SQL. Например, сложные выборки/обновления (особенно, аналитические отчеты с оконными функциями и рекурсивными CTE). Компромиссным вариантом является маппинг результатов нативных запросов на объекты, Доктрина это позволяет. В споре ORM vs SQL не победил никто. Тем временем, всех кто намучался с ORM, тормозящими запросами и плохой производительностью в рабочих ситуациях, приглашаем на PG Day'17. У нас подготовлено для вас множество различных докладов и мастер-классов для самых разных баз данных!