data mapper паттерн php
Active Record против Data Mapper-а для сохранения данных
Эти 2 шаблона проектирования описаны в книге Мартина Фаулера «Шаблоны корпоративных приложений» и представляют собой способы работы с сохранением данных в объектно-ориентированном программировании.
Пример шаблона Active Record
В этом упрощенном примере, дескриптор базы данных вводится в конструкторе Foo (Использование инъекции зависимостей здесь позволяет тестировать объект без использования реальной базы данных), и Foo использует его, чтобы сохранять свои данные. Do_something — просто метод-заглушка, заменяющий бизнес логику.
Преимущества Active Record
Недостатки Active Record
Пример Data Mapper-а
В данном случае, класс Foo намного проще и должен беспокоиться только о своей бизнес-логике. Он не только не должен сохранять собственные данные, он даже не знает и не заботится о том, все ли его данные были сохранены.
Преимущества Data Mapper-а
Недостатки Data Mapper-а
Сервис-объекты
При использовании шаблона проектирования Data Mapper, вызывающий код должен выбрать Mapper и бизнес-объект и связать их вместе. Если это код вызова в контроллере, то в конечном счете ваша модель «утекает» в контроллер, что может вызвать большие проблемы при поддержке и юнит-тестировании. Эта проблема может быть решена путем введения объекта-сервиса. Сервис является воротами между контроллером и моделью и связывает доменный объект с Mapper-ом по мере необходимости.
Следует помнить, что M в MVC, представляет собой слой абстракции модели, а не объект модели. Так может быть несколько типов объектов в одной модели (в приведенном выше примере, у вас может быть объект сервиса, доменный объект и объект Mapper-а, выступающие в роли единой модели). С другой стороны, если вы используете модели Active Record, ваша модель может быть представлена лишь одним объектом.
Варианты использования
Объекты Active Record исторически были очень популярны из-за того, что они проще, легче в понимании и быстрее в написании, поэтому многие фреймворки и ORM используют Active Record по умолчанию.
Если вы уверены, что вам никогда не понадобиться менять слой сохранения данных (если вы имеете дело с объектом, который представляет из себя INI-файл, например), или вы имеете дело с очень простыми объектами, в которых не так много бизнес-логики, или просто предпочитаете держать все в небольшом количестве классов, тогда шаблон Active Record это то, что вам нужно.
Использование Data Mapper-а хотя и ведет к более чистому, простому в тестировании и поддержке коду, и обеспечивает большую гибкость, — цена этому, — повышение сложности. Если вы еще не пробовали его использовать, то дайте ему шанс, — вам должно понравиться.
fesor / README.md
Существует 4 основных подхода для организации рабоыт с базой данных, Table Gateway, Row Data Gateway, Active Record и Data Mapper. Все эти подходы объеденяет то, что они скрывают от нас базу данных и нюансы работы с ними (в частности SQL). На сегодняшний день самым популярным подходом являются Active Record и Data Mapper, все о них слышали, но для того что бы более полно представлять, как развивалась идея, стоит рассказать и о первых двух.
Table Data Gateway
Можно было бы конечно сделать объект, и держать логику хранения данных внутри. Но в таком случае у нас наши сущности будут зависеть от базы данных, и их будет не так уж легко тестировать. Тесты будут требовать подключения к базе данных и от того станут медленными.
Для решения этой проблемы, нам нужна прослойка между сущностью и базой данных. Row Data Gateway. При этом подходе мы проэцируем отдельные ряды нашей таблицы на объекты, которые служат промежуточным звеном и инкпсулируют все детали о том как сохраняется информация в себе. В итоге наши сущности могут работать через этот gateway используя штатные средства языка программирования.
Отдельно стоит заметить, что поскольку Row Data Gateway представляет собой отдельную строку таблицы, для выборки нужных рядов нам нужно уже делать отдельный компонент Finder.
AR и Row Data Gateway не очень удобно тестировать. Нам хочется полной независимости от базы данных.
Object Relation Mapping
Поскольку первые два подхода практически не используются, рассмотрим Active Record и Data Mapper. В чем координальное отличие?
В рамках Active Record (или Active State) мы работаем с объектами как отображением элементов нашей базы данных, как если бы у нас был прямой доступ к ним без SQL прослойки. В DataMapper мы работаем исключительно с нашими объектами, которые лежат в памяти, и просим отдельную штуку (мэппер) что бы тот синхронизировал состояние объектов в памяти и в базе (сохранил состояние по сути).
Доустим у нас есть некий граф объектов, с которым мы работаем в рамках бизнес транзакции. И мы должны сохранить изменения в базу, то есть объект на верху графа и все связанные с ним сущности. Если руководствоваться паттерном «Информационный эксперт», заниматься этим должен тот, кто знает как это делать. В случае AR эта логика выносится прямо в сущности и мы явно задаем в каком порядке что сохранять. В случае же с DM у сущности нет таких знаний и мы выделяем все в отдельный компонент, что дает нам дополнительные варианты как это организовать удобнее для нас. От тупого разруливания графа «руками», до алгоритмов, которые разруливают это автоматически.
На негодняшний день в чистом виде в рамках существующих ORM вы можете встретить только DataMapper. ORM на основе Active Record так или иначе внутри используют намного более сложные концепции, дабы упростить разработчикам жизнь и снизить сложность.
Реализации ORM использующие Data Mapper
Реализации ORM использующие Active Record
codedokode / Паттерны работы с базой данных.md
Это старая версия урока, которая больше не обновляется. Новая версия расположения тут: https://github.com/codedokode/pasta/blob/master/db/patterns-oop.md
Паттерны работы с базой данных
Разберемся, как правильно с применением ООП сохранять и загружать данные из базы. Существуют такие подходы:
В этом варианте мы не используем никаких классов, а просто загружаем/вставляем данные в базу с использованием PDO:
Этот способ не требует создания никаких классов, но он очень ограничен и ведет к плохому коду если в нашем приложении больше 1-2 таблиц: ты начнешь путаться, где какой массив и какие у него поля. В общем, с таким подходом ничего хорошего нам не светит.
Однако этот подход наиболее эффективен при работе с огромным количеством записей.
Такой подход например поддерживается классом Zend_Db_Select в Zend Framework 1: http://framework.zend.com/manual/1.12/ru/zend.db.select.html
Заметь, что в класс мы можем поместить вспомогательные методы, работающие с этой новостью — удобно (хотя стоит ли вставлять валидацию в сущность — спорный вопрос так как в нашем варианте у нее нет доступа к БД и она например не может проверить заголовок на уникальность — чтобы это было возможно, надо переносить валидацию в другое место). Теперь у нас есть новость, давай посмотрим, как можно сохранить или загрузить ее из базы данных.
Код, реализующий загрузку и сохранение сущностей в SQL базу данных еще называется ORM (Object-Relational Mapper). ORM пытаются избавить нас от необходимости писать однотипные примитивные SQL запросы, позволяя нам работать на более высоком уровне.
Это более простой способ. При его использовании методы для сохранения/загрузки сущности из БД добавляются прямо в нее. Чтобы не копипастить их в каждый класс, их обычно добавляют в базовый класс, а сущность наследуют от него. При этом обычно в сущности делается метод, возвращающий информацию о соответствии полей объекта таблице и полям в базе данных (чтобы можно было правильно составить SQL запрос):
Соответственно, вот как выглядит пример поиска записей, вставки и обновления записи:
Также он использовался в Doctrine 1.
Этот подход относительно прост, но он имеет недостаток: мы смешиваем бизнес-логику (методы работы со свойствами новости) и работу с БД в одном классе. Хотя объект-новость вполе может сущестовать и сам по себе. Для решения этой проблемы нам нужен DataMapper.
В DataMapper мы выносим код сохранения/загрузки сущностей (и все что связано с базой данных) в отдельный класс. Вот пример такого класса:
И вот пример использования:
Этот подход используется в ORM Doctrine2: http://odiszapc.ru/doctrine/ Только там Mapper называется Repository.
Doctrine 2 — это библиотека реализующая паттерн DataMapper. Ты просто добавляешь в свои сущности аннотации, задающие соответствие полей объектов и полей в базе данных, а Doctrine дает тебе классы-репозитории, которые позволяют загружать и сохранять твои объекты в базу данных. Doctrine 2 — очень мощная и популярная, хотя и непростая для начинающего, библиотека. Чтобы с ней работать, надо понимать саму идею ORM, паттерны UnitOfWork и IdentityMap. И придется много читать мануал по ней.
Напишу еще несколько вещей, которые мы не рассмотрели, но которые есть в больших ORM вроде Doctrine 2:
Doctrine 2 не требует от тебя унаследовать класс-сущность от какого-то базового класса, он позволяет связать любой класс с базой данных — главное чтобы в нем были методы get../set.. для чтения и записи полей. Также, придется потратить время на то, чтобы разобраться, как правильно использовать этот ORM и как настроить в нем кеширование метаданных, чтобы он работал с приемлемой скоростью.
В общем, если у тебя маленькое число таблиц, то ты можешь попробовать обойтись простым DataMapper. Но если у тебя много таблиц, и есть связи между ними то использование Doctrine 2 поможет отойти от написания SQL запросов к манипуляции объектами, сделать код проще и короче и сэкономить твое время. Если же у тебя высоконагруженный проект, то возможно от сложных ORM придется отказаться.
Data Mapper — какие классы за что отвечают?
Где проходят границы между зонами ответственности классов при использовании паттерна Data Mapper?
Если я правильно понимаю логику паттерна, таких классов как минимум три: собственно маппер, объект предметного слоя и репозиторий. И если с разделением базовых (CRUD) задач всё понятно, то не понятно, какой класс должен отвечать за поиск.
Допустим, в маппере есть метод find(), который по идентификатору находит нужную запись и возвращает готовый объект предметного слоя. Это в теории, а в практике в реальных системах условий поиска гораздо больше: модель оборудования можно искать не только по идетификатору, но и (к примеру) по производителю, категории, стоимости, дате производства.
Ситуация сильно усложняется, если атрибуты в объекте предметного слоя отличаются в написании от названий полей в базе данных (например, producerId в коде и producer_id в БД).
Ну и еще более ситуация усложнится, если в системе есть несколько родственных объектов и используется наследование с единой таблицей. Маппер спокойно берет название класса из БД с прочими данными и генерирует тот или иной объект.
Итак, о поиске. Получается, нужно создавать еще один класс, который будет отвечать за поиск? А если маппер возвращает один из трёх возможных объектов, то таких классов должно быть уже три?
Подпните, пожалйста в нужную сторону.
1 ответ 1
Теперь, понятное дело, что в коротком ответе я не смогу Вам передать в двух словах содержание целого раздела книги и выводы из ознакомления с исходниками Doctrine, но «подтолкнуть» попробую 🙂
Про наследование: преобразователь должен знать какую сущность на какую таблицу он преображает. В целом, опять же советую посмотреть как эта задача решается в том же Hibernate.
Правильно ли я понимаю, что дата маппер предполагает одну сущность = одной таблице (это даже в ответе, в последнем абзаце видно)?
Это комплексный вопрос и разбираться с ним стоит, беря за теоретическую базу не последний абзац моего ответа (в котором, как я упомянул уже, я не смогу полностью раскрыть тему при всем желании), а хотя бы упомянутую в первом абзаце книгу М.Фаулера (которую всяко полезно почитать любому разработчику).
Еще раз советую: купите-скачайте и почитайте Фаулера, если хотите действительно разобраться в вопросе. Из отрывков коротких ответов на SO у Вас не сложится красивая структурированная полочка последовательных знаний по такому комплексному вопросу. Более того (и судя по вопросу «маппер = таблица?», это уже в той или иной мере происходит), из этих отрывков у Вас может сложится верная в отдельных частях, но не верная в целом картина понимания по этому (стоит заметить очень важному для всякого разработчика ориентированного на enterprise) вопросу.
Вот Вам в качестве бонуса немного ссылок по Доктрине, может помогут:
Data Mapper pattern and duplicate objects
I’m using the data mapper pattern in a PHP app I’m developing and have a question. At present, you request a Site object with a specific ID and the mapper will lookup the row, create an object and return it. However, if you do this again for the same Site you end up with two different objects with identical data. eg.:
So, my question is, should I:
3 Answers 3
What you’re looking for is the Identity Map pattern. Be careful with so called «reading inconsistencies», though. While you use an «old instance», the DB might have been changed already. And while you edit your object, another user might get an instance of it, change it faster and save it faster. Then the other object overrides all these changes again. On the web though maybe not such a big problem since a «page» quickly runs through and no object survives for longer than a few fractional seconds.
I know the question was asked quite a while ago still wanted to answer just in case if someone else runs in a similar dilemma. Actually the above suggestions #1,2,3 that author made are all related and one should consider them all in order to solve the problem.
2) Yes ideally there should always be one instance of the given data mapper (Site_Mapper) in order to maintain a single instance of the IdentityMap at a given time. This can be done using Singleton pattern. This is possible with some getter method like Site_Mapper::getInstance() which will always return the same instance of a given mapper. You’d also have to declare the __construct() as a private method to prevent unwanted instantiation using new and make sure getInstance() is the only way to instantiate a mapper.
3) What the author mentioned above about static properties is true as well. To implement a Singleton in PHP one has to use a static property to hold and instance of a Mapper.
I would highly recommend Martin Fowler’s book «Patterns of Enterprise Application Architecture» which talks about the above mentioned patterns and many more. It’s a good read especially if you’re working on your own custom ORM solution. Hope that helps.
