Pull to refresh

Comments 62

А почему топик не обозначить как «перевод»?

Во вторых выводы какие-то оч странные. Как будто это мы сами всегда пишем реализации ОРМ.
Если мы работаем с Доктриной, мы вряд ли будем дописывать к ней слой ActiveRecord, работая с ОРМ в других фреймворках (Laravel, Yii), дописывай сколько угодно, а обьект всё равно будет знать как себя сохранить, что совершенно нивелирует паттерн датамаппер. О чем эта статья? Репозитории — это хорошо, ооокей?

— Я кстати подозреваю что статью писал ларавельщик. У них очень печально с аргументацией зачем нужен датамаппер и почему он лучше.
Ну плюсы/минусы спорные, наверное, вы правы.
Мне больше была интересна часть про их принципиальные отличия, ибо я очень слабо их различал.
Я не нашел как обозначить топик переводом, у меня есть только вариант «Обучающий материал» или я просто не там ищю, подскажите?
Как-то так: minus.com/l4aPf83jiwIIZ

Да, если что я плюсик вашему посту поставил. А мои претензии к автору оригинального поста. Обзор паттернов это хорошо, но «нужно юзать репозиторий, потому что это модно» — явно не аргумент. Хотелось бы, чтобы в РНР сообществе прекратили делить паттерны на модные/не модные.
Спасибо, но у меня этого нет, наверное потому что пост перекочевал из песочницы.
Да я то с вами согласен, я вообще против фанатизма любого рода, надо как-то больше на задачи ориентироваться, — где-то data mapper, где-то active record использовать, а где-то и вообще PDO и SQL хватит и не нужно усложнять.
Здесь не вопрос Модно-Немодно.
Выбор конкретного подхода Active Record vs Data Mappers + Repository должен вытекать из бизнес задач.
Дело в том, что Active Record является фактический неотъемлемой частью RAD (rapid application development), и цель его быстро развернуть уровень доменных моделей (читай бизнес логики) по существующей схеме базы (как правило, небольшой). Фактически все RAD проекты строятся по принципу «Database First» — т.е. мы вначале проектируем схему БД, как-то ее декларируем (Json, Yaml, SQL, ini) и через эту декларацию фреймворк генерирует нам доменные модели с уже реализованым CRUD функционалом через механизм scaffolding-a.
Это хорошо работает на маленьких-средних проектах с небольшими зависимостями. Если же проект большой и людей, которые работают на нем много, и чтобы поддерживать все в рабочем состоянии эти люди хотят покрывать код тестами, то тут начинаются проблемы…
Поэтому в последнее время такую большую популярность и обрел DDD (Domain driven design), который декларирует одним из своих основных принципов persistence ignorance и «Code First». При этому мы получаем все те плюшки, которые автор описал в статье (SOLID, доменные модели не знают вообще, что их куда-то сохраняют, поэтому становятся тонкими и содержат только бизнес логику). Развивая идею дальше мы приходим к тому, что в таком виде можно легко реализовать Unit of Work паттерн, и разгрузить слой приложения (читай контроллеров) от обязанностей сохранять и контролировать что модели сохраняются и обновляются корректно. А эта обязанность переходит в инфраструктурный уровень. Но при этом, да, сложность проекта становится выше.
Ваш комментарий стоит всего этого поста. Если бы тот же уровень аргументации был в основном посте… Но увы, в самом посте нет никаких намеков на то что автор хорошо понимает оба паттерны и все вытекающие. «дайте ему шанс, — вам должно понравиться.» звучит как «попробуй, это модно в нынешнем сезоне!»
Недостатки DataMapper надуманные.
Дольше думать над чем? Управление сущностями ничем не отличается, кроме $dm->save($user); $user->save(); Но это на мой взгляд вообще не отличие. Ровно как и преимущество DM над AR в том что невозможно измнить mapper, просто не надо хардкодить PDO в конструкторе вот и все. На основе той же доктрины можно сделать AR, они сами описали в кукбуке этот момент.
В целом я с вами согласен, что при использовании, по большому счету, все равно, у какого объекта вы save() вызываете. Но все же, если не рассматривать экзотические варианты, вроде AR поверх Doctrinы, то как правило, DM — получается более сложной абстракцией со всеми вытекающими последствиями. Но, наверное, это для вас будет иметь значение только, если вы разработчик, а не пользователь.
Не понимаю какие вытекающие, если честно)
Уровень абстракции повышается, сложность стало быть. Но это больше разработчиков касается конечно, чем пользователей.
Сложность бывает разная. Имхо, десять простых классов поддерживать намного проще, чем один «божественный».
Я согласен, что в конечном счете все от реализации зависит, но более высокий уровень абстракции так или иначе повышает, скажем так, базовую сложность.
Все проблемы программирования можно решить дополнительным слоем абстракции… кроме проблемы избыточной абстракции ©
да, это я и имел в виду)
У Active Record постоянно возникают проблемы при сериализации, так как подключение к Базе данных сериализовать нельзя. Это одна из кючевых аргументов против Active Record. При этом, если подключение не сериализовать (__sleep, __wakeup), то нужно далее вручную его подсунуть, а при условии использования ServiceLocator'ов это становится проблематичным.
оО вот уж не думал что это проблема. Зачем тогда придумали интерфейс Serializable? Просто раз уж AR — кеширование должно быть через сам объект статическим методом
Есть ситуация, нужно Entity засунуть в Сессию (Entity юзверя залогиненного).
Если использовать Active Record, то нужно при сериализации убрать оттуда подключение к БД.
При восстановлении из сериализованных данных, соединения в User Entity нет. Его можно взять из ServiceLocator'а, но в момент восстановления данных ServiceLocator неоткуда получить (Singleton — не предлагать).
В итоге получается ситуация, что соединение засовывается вручную, и это нужно постоянно контролировать.
А в чем проблема инжектить в AR-сущность кешер и загружать из кеша?
А зачем танцевать с бубном, если можно задачу просто облегчить? У Entity есть Аннотации, по которым можно спокойно определить, и что за таблица, и управляющий Репозиторий.
Рекомендую почитать про Doctrine ORM, и причины почему Symfony сделала по дефолту (вместо Prorel) именно Doctrine.

Конечно в реальных проектах Entity Manager у Doctrine ORM очень прожорливый (ну уж очень), но на основе Аннотаций можно быстро написать свою ORM, с блекджеком и…
Я целиком и полностью за дата-мэппер, и юзаю доктрину) Но я все это пишу к тому, что AR по этим аргументам никак не проигрывает DM.
DM на мой взгляд идеологически более правильна, но к сожалению доктрина тяжела и потому думаю не особо распространена.
Рекомендую почитать про Doctrine ORM, и причины почему Symfony сделала по дефолту (вместо Prorel) именно Doctrine.

Насколько я помню, то тут есть нюансы:
* пропел давным давно как мертв (ок, ещё немнго трепыхается, но не суть)
* это произошло ещё в версии 1.2 симфони, когда была доктрина 1.х с реализацией ActiveRecord.
* доктрина единственная серьезная ОРМ которую можно юзать без привязки к фреймворку.
Уже два года как, или даже больше…
Но релиза как не было так и нет.
Что мешает использовать аннотации в Active Record? Что мешает в Active Record назвать таблицу репозиторием?

Имхо, вы совсем не понимаете сути data mapper'а. Она не в синтаксическом сахаре, она в отделении данных от логики их сохранения. Более того, она вообще в правильном использовании ООП.

И плюсы её очевидны (но совсем не те, про которые писали вы) — гибкость и разделение ответственности.
В Yii в Active Record есть метод, возвращающий имя таблицы, и при желании можно указать префикс, обозначающий коннект, который обеспечивает доступ к источнику

class User {
    function table() { return 'db1.user'; }
}

«table» не сериализуется, он же метод
При восстановлении объекта коннект восстанавливается лениво через локатор Yii::app()->db1 только при чтении или записи объекта

При тестировании в локатор можно подсунуть настройки тестовой базы или мок

Я думаю, это достаточно гибко
В книге на самом деле 4 шаблона, ну да ладно… Касательно DataMapper лучше всего добавить в объект поле для хранения ссылки на экземпляр мапера, что позволит использовать просто $object->save() и не задумываться о том как был создан объект (эта связь все равно понадобится если захочется реализовать прозрачную выборку связанных объектов)

Давно мучает небольшой вопрос: а какая библиотека позволяет отслеживать изменение свойств объектов и динамически менять связанные объекты? (см код)

// Объект
$object = new MyObject([id = 2, parent_id = 1]);

// Родительский объект (ленивая загрузка связанного объекта)
$parent = $object->parent; // MyObject([id = 1, parent_id = null]);

// Дочерние объекты (ленивая загрузка связанного объекта)
$childs = $parent->childs; // [MyObject([id = 2, parent_id = 1]), MyObject([id = 4, parent_id = 1])]

// "Магия"
$object->parent_id = 3;               
// $object->parent === MyObject([id = 3]) - новый родитель
// $parent->childs === [MyObject([id = 4, parent_id = 1])] - объект удалился у предыдущего родителя

$object->parent    = new MyObject([id = 3]);
// $object->parent_id === 3
// $parent->childs    === [MyObject([id = 4, parent_id = 1])]
Вы опять спустились до уровня SQL. ID — это свойство сущности, но вы работаете с объектами. Доктрина позволяет делать так:
$user = new User();
$user->setFriend($em->find(3, 'Blabla\User'));

В данном случае на место друга встанет прокси объект с единственным определенным свойством — id:3, до тех пор пока вы не запросите какие-то свойства друга — ничего из базы загружено не будет. Вроде как можно даже что-то поменять и опять же грузиться ничего не должно, только обновится при $em->flush()
Нет, скорее перешел к полноценному ООП: если меняются свойство то это изменение глобально и действует сразу, без всякого $em->flush() и подобных. Т.е. если меняем $object->parent то в $object->parent->childs должен сразу добавиться $object*

* — понятно что это возможно только если все объекты хранятся в памяти, но если их немного это не проблема.
В ООП вы управляете объектами. Представим что вы — объект, человек. От того что я у вас номер на машине поменял — у вас машина тут же не обновилась до соответсвующего номера? Более того невозможно проверить существует ли машина с таким номером без запроса в базу.
ID — это то, что связывает сущность внутри приложения с строкой в таблице в базе. Это нельзя менять вот так вот просто
Изначально $object->parent->childs содержит какой то набор объектов («первый набор»), меняем $object->parent и… и ничего, $object->parent->childs по прежнему содержит «первый набор», хотя в реальности этот набор уже изменился — в этом то и проблема (обычно она решается ручным релодом $object->parent->childs, что неудобно и не логично)
Ничего не понял, можно в коде пример привести? Вы имеете ввиду что $user->setGroup($group) а потом нужно $group->addUser($user)?
Вы имеете ввиду что $user->setGroup($group) а потом нужно $group->addUser($user)?

Именно (только вот это $group->addUser($user) должно быть полностью прозрачно).
Похоже на то что нужно, но немного непонятно: будут ли обновлены объекты до вызова flush() в примере с OneToMany?
Эта ваша ответственность следить за правильностью ссылок из объектов друг на друга. Если ваш код поддерживает это (т.е в коде метода addUser вы устанавливаете $user группу), то никаких проблем с Doctrine у вас быть не должно.
Зачем делать вручную что-то что может быть автоматизировано? ;) И потом, это очень удобно и позволяет полностью забыть о самом мапере и просто использовать нужные объекты со всегда актуальными свойствами.
В Doctrine они и так будут актуальными, если вы правильно устанавливаете связи между объектами, как написали вам здесь.
Это особенности реализации, а не свойство паттернов. Было бы прекрасно, если бы все можно было вот просто так взять и поменять — проблема в том, что это очень сложно реализовать.
Парадигма ООП не вводит понятие аспекта.
В данном случае вы пытаетесь ввести аспект связей(древовидной) объектов и сохранить его целостность.
А это, увы, уже задача бизнес-логики.
Возможно, но при наличии данных об этих связях целостность может быть обеспечена автоматически, что гораздо удобнее чем везде обеспечивать её вручную.
ООП не про связи объектов, так же как не про здоровье программистов. Это лишь парадигма программирования, в которой чётко указаны термины и теоремы.
Скорее не только про (или aгрегация тоже не относится к ООП?)
Агрегация — это не связь объектов, это отношения классов.
Эти отношения классов, являются способом реализации полиморфизма.
Как раз таки связь (один объект вложен в другой).
Не надо смешивать семантическую связь и механизмы отношений.
А чем связь отличается от отношения в контексте ООП?
Связь — семантические отношения между экземплярами классов, другими словами это уже некие отношения основанные на значении класса для программиста в контексте задачи, а не в контексте теории ООП.
Агрегация, Композиция и Наследование — отношения между объектами/классами в контексте ООП, без учета семантики.
public function setParent($parent)
{
    $this->parent->removeChild($this);
    $parent->addChild($this);
    $this->parent = $parent;
}


А у вас не ООП, а каша. С таким уровнем «магии» ваш проект уже завтра станет неподдериваемой шляпой.
Т.е. вы всерьез хотите сказать что неактуальность текущей иерархии объектов это правильный ООП подход? :) Ну как хотите, если нравиться самому синхронизировать все объекты — синхронизируйте, кто ж запрещает…
Опять же, не смешивайте ООП и целостность связей объектов бизнес-логики.
ООП не про это.
Встанет таки полный объект, для прокси нужно использовать em->getReference().
Чем в приведённом вами примере DataMapper отличается от TableGateway?
На сколько я понимаю, Table Gateway объект является Data Mapper объектом, но не наоборот.
Table Gateway — это частный случай, в данном случае он и есть. Data Mapper — более общая вещь, он мапит на что угодно — совершенно необязательно в целевом хранилище вообще есть понятие таблицы.
Ну это я понял, спасибо :)
Я как бы намекал, что в статье, по сути, сравнивается Active Record и Table Gateway.
Тема Data Mapper'а раскрыта не полностью.
На мой взгляд в статье лишь сравниваются два подхода. Не стояла задача полностью рассказать о каком либо из них.
Не упомянут важная фича DataMapper'a — он тесно привязан к соответствующей сущности, и это увеличивает связанность (coupling), в то время как сущность, сама себя сохраняющая, наоборот, обладает более высокой связностью (cohesion). С другой стороны, мапперы примитивны, лежат в отдельном слое, друг с другом не связаны, и модифицировать их достаточно просто.
Недостатки: Вам придется гораздо больше думать, перед тем как написать код.
я бы не назвал это недостатком
Сейчас почти все AR позволяют легко менять сторадж, за это ответственен DBAL. Даже в примерах их статье видно, что для DM оказывается так же зависим от PDO.
Гм. Это всё прекрасно, но вот у меня например возникает такой вопрос: а что думает DataMapper о прозрачной массовой ленивой подгрузке данных для коллекции объектов?
Тут правда вопрос, что об этом думают и существующие AR?
Сам подход — это я у себя обычно применяю, идея его в том, что объект помнит, частью какой коллекции является, и при обращении к геттеру свойства, требующего подгрузки данных из БД, подгружает и прописывает свойство сразу для всех объектов в «своей» коллекции.
Получается, что и писать удобно (не надо явно просить подгрузку свойств), и работает быстро — и лениво, и никогда нет сотни селектов в цикле, отдельных для каждого объекта.
Sign up to leave a comment.

Articles