Pull to refresh

Comments 81

Жесткую привязку к 'id' надо заменить primary key
Еще вопрос будет ли оно корректно работать с relations child->parent и не лезть в базу за одним и тем же parent для 10 child?
В базу оно будет лезть точно так же как и раньше, преимущество приведенного метода только в том, что не будет путаницы с одинаковыми объектами разнесенными по всем скриптам. Т.е. оно будет лезть 10 раз, но объект parent будет создан только один.
Чтобы не лезло в базу нужно перехватывать момент «перед запросом», но без создания новой прослойки между моделью и CActiveRecord так, увы, сделать не получится.
Метод по ссылке предполагает наследоваться от AActiveRecord, а это, согласитесь, как-то странновато…
Хотя это, пожалуй, недоработка фрэймворка, что нельзя по другому решить проблему.
Ну у меня модели итак наследуются от моей модели, что мешает мне мою модель унаследовать от AActiveRecord вместо СActiveRecord?
длинная цепочка наследования — это не есть хорошо.
Опять же они и наследуются, чтоб не городить ваш код protected function instantiate в каждой модели
Опять же, в данном случае это недоработка фрэймворка.
И вот такое ConcreteModel < — AActiveRecord < — BActiveRecord < — CActiveRecord отнюдь не признак хорошей архитектуры =)
А я думал это просто механизм ООП…
«Предпочитайте композицию наследованию» (с) =)
Что именно? Если Вы про такую структуру классов, то искать решения без наследования. С помощью тех же events. Но, как я писал ниже, нельзя перехватить событие перед отправкой запроса. Во время CActiveRecord::beforeFind() модель и все остальное пусто и нет возможности узнать по какому Id запросили модель(я по крайней мере не нашел).
getRecord принимает строку, а addRecord — объект. Послелний вариант, имхо, лучше.
Зачем нужно инстанцирование у ObjectWatcher, не лучше ли ограничиться статическими методами?
Странно то, что Active Record в Yii не включает в себя Identity Map.
Попробуйте им доказать, что оно им надо, очень удивитесь, мне хватило просьбы отключить double encode в htmlspecialchars…
Как раз одна из причин по которым все таки свой фреймворк бывает предпочтительнее в некоторых случаях. Благо есть время им заниматься.
Отключение double encode действительно штука очень сомнительная потому как поощряет делать лишний раз encode. Так можно и до отключения notice во view дойти.
Не надо тут начинать то же самое с высосанными из пальца примерами
С ним есть определённые проблемы. См., например, ниже про Rails.
1. Зачем два раза выбирать одну и ту же модель?
2. Какой смысл в indentity map, если всё-равно делается запрос к базе, даже если модель уже имеется.
1. child->parent
2. какой смысл делать запрос в базу, если его результат в виде объекта уже есть?
1. М?
2. Например, для того, чтобы убедится, что результат более-менее актуален. Или для того, чтобы получить чистый объект, а не тот, с которым мы, ещё не сохранив, поработали.
1. в одном месте вытянули модель через отношение, в другом месте обратно. Да разве не может быть случая, когда в рамках запроса требуется дважды работать с объектом в разных контекстах?
2. То что делается запрос — это недостаток, но его нельзя решить нормальным методом силами фреймворка, т.к. нет возможности перехватить момент «перед запросом».
1. Может. Если это всё read, то проблемы нет. Если не только read, может стать сложно и плохо.
2. beforeFind выполняется перед запросом и в нём доступен критерий запроса.
$this->getDbCriteria()? Если да, то в нем:
CDbCriteria#1
(
[select] => '*'
[distinct] => false
[condition] => ''
[params] => array()
[limit] => -1
[offset] => -1
[order] => ''
[group] => ''
[join] => ''
[having] => ''
[with] => null
[alias] => null
[together] => null
[index] => null
[scopes] => null
[CComponent:_e] => null
[CComponent:_m] => null
)
А как обратиться к этому «скрытому» параметру? =)
Я думаю только подписавшись напрямую на событие onBeforeFind, затем уже его обрабатывать и брать из него criteria как $event->criteria; иначе вы просто перекрываете реализацию CActiveRecord beforFind();
И да, не забывайте вызывать parent::befourFind() конечно чтобы событие «всплыло», там впринципе написано =)
А можно небольшой ошмёток кода для вопроизведения?
в модели
protected function beforeFind() {
    CVarDumper::dump($this->getDbCriteria(),10,true);
}

Вызываю через findByPk как указано в топике.
Баг. Я про него совсем забыл. Думал, что поправлен давно.
Да я все о том же, о наболевшем, что мои несчастные 10 child дергают из базы 10 однояйцевых parent, не более…
Умгу. Да, это нормальный, в общем, пример. Тут действительно пригодилось бы.
А разве кеширование не может решить ту проблему о которой пишет Dr_Death?

Ведь по большому счету эта проблема проектирования.
Т.е. даже Identity Map нету, потому что вдруг нужен актуальный объект и прочее, а мне предлагают объекты загнать наглухо в кэш. Помниться как то просил кэшить child->parent только на время выполнения скрипта, чтоб на одной странице много раз не вызывался и юзал результаты первого запроса, так и то столько про актуальность чего то там наговорили, а тут фигачить все в кэш и не жаловаться…
Тут смотря какая задача. При выборках обычно актуальность не так важна, как при изменении данных.

Я тут подумал, случай с parent-child тоже не сильно однозначный. Если нужно построить дерево, выбирать лучше всё и сразу. В остальных случаях, скорее всего, можно обойтись без identity map и дополнительной выборки, если поменять исходную точку запроса. Если более конкретный пример дадите, можно будет попробовать решить красиво без identity map.
Т.е. 10 parent которые по сути один и тот же, но на самом деле 10 разных, можно изменять по разному в разных местах и при этом говорить о какой то актуальности измененных данных?

Мне пока lazy load нравиться больше, чем лазить каждый раз и дописывать join и прочее with вместо child->parent->parent
Зависит от конкретного примера. Может у вас вообще read-only дерево. Тогда можно что угодно делать и как угодно.

Lazy load так и так будет менее эффективен, чем eager. Даже с identity map. Это, конечно, если не кешировать.
Как раз таки непонятно что надо делать с parent чтоб он был нужен в разных местах, еще и с разными данными внутри еще и не сохраненными, без абстрактных если бы да кабы.

lazy load удобней, посещаемость не 10 000 000 человек, чтоб начинать экономить
В принципе, если после каждого чиха сохранять данные в базу, то да, проблем не будет.
И что за задача такая, где нужно узлы дерева за один реквест менять много раз и при этом не сохранять?
Дак это как раз вам вопрос, зачем parent размазан по куче переменных и о какой актуальности идет речь если его можно менять в разных местах по разному? Это не моя задача, это ваши аргументы об какой то актуальности… Моя задача чтоб он был один единственный для всех…
Чистый объект можно получить через какой-нибудь getClean() от объекта, а обновить объект через, допустим, reload(). Вместо этого фреймворк выделяет память под два объекта, да еще и делает два запроса.

Еще было бы неплохо использовать UnitOfWork, но вряд ли это впишется в текущию концепую, да и в ActiveRecord в часности
Смысл может быть в том что нужена именно актуальная версия объекта, а не закешированная при первом вызове, вообще тема топика как-то высосана из пальца и наталкивает на то что ТС еще не совсем понял Yii.
Dr_Death твой пример совсем сомнителен и глуп по большей части, никто из нормальных разработчиков такое не поддержит, у тебя просто «глубокая обида» на то что твой «важный» патч не приняли?
Ну наверно тогда проще дернуть один раз с параметром отключающем кэширование когда это действительно надо, чем постоянно дергать одно и тоже, когда это не надо. А дергать X раз parent для child->parent для вывода например ссылки на child, конечно не глупо. Обиды никакой, дописал руками и забыл про косяки, это проще и быстрей, чем слушать сказку про бычка.
ИМХО если Вам нужен IdentityMap, то у Вас проблемы в архитектуре приложения. Не понимаю зачем люди плодят название, если это по сути патерн Register…

Рекомендую почитать: «Патерны Проектирования» Гамма Эрик

Вы скрывает логику в защищенном методе, последующее поведение экземпляра класса не очевидно.
Identity Map нужен, чтобы без проблем вызывать всякие $user->getArticles() несколько раз не кешируя в переменную, да и просто вызывать ->findByPk($item_id) зная, что не будет лишнего запроса. Почему-то JPA, Hibernate, Entity Framework, Rails Activerecord этого придерживаются, но Yii упирается рожками.
По крайней мере в Rails эта штука по-умолчанию выключена. В комментариях написано «не включать, убьёт». В документации приведены примеры удаления лишних записей при включении.
Угу, только там проблема с ассоциациями. Для единой модели все очень даже живуче. Плюс рельсы еще и сам запрос скеширует id=`id`
Как раз оттого, что всё очень нетривиально для связей, этого ещё нет в Yii.
Так нету потому что для связей сложно, или потому что «чтобы убедится, что результат более-менее актуален. Или для того, чтобы получить чистый объект, а не тот, с которым мы, ещё не сохранив, поработали»? Ведь чистый объект или обновленный можно решить простым методом в модели.
Если это решить методом в модели, поломается обратная совместимость, что для ветки 1.1 нехорошо.
Я думаю, нету, потому что гладиолус, самая вероятная причина
С чтением тут всё ясно и хорошо. Проблемы могу всплыть при записи.
Почему бы явно не положить объект в реестр?
$record = $someResponsibleObject->getRegister()->get(10);
if (!$record) {
     $record = $repository->findByPk(10);
     $someResponsibleObject->getRegister()->add($record->getId(), $record);
}


Зачем мешать логику реестра и ектив рекорд? Будут потом проблемы с тестированием;
А если хотите вызывать $user->getArticles() создайте свойство $_articles;
public function getArticles() {
     if (!$this->_articles) {
          $this->_articles = parent::getArticles();
     }
     return $this->_articles;
}
Потому что хочется сохранить интерфейс нетронутым. Полиморфизм, знаете ли. Т.е.не клепать костыль при вызове, а изменить поведение, оставив интерфейс прежним.

>> Зачем мешать логику реестра и ектив рекорд?
Звучит как «зачем мешать mvc и?
В данном случае вызов объекта захардкодженый. Register по сути выполняет работу некого кэша (на один http запрос). Где Вы видели, чтобы драйвер кэша был захардкоджен?
> Не понимаю зачем люди плодят название
В той же книге Гаммы Эрика написано, что многие паттерны очень похожи, но преследуют разные цели и из-за этого имеют разные названия (можно вспомнить про bridge/adapter/proxy из паттернов проектирования).
Эрих сотоварищи хотели как лучше, предположили, что будет рай, если дать каждому подходу по имени. Одного не учли эти парни с благими намерениями — есть в мире люди, тонко чувствующие всевозможные оттенки чего бы то ни было. А какой художник откажется дать имя, найденному им самим оттенку, ведь это шанс прикоснуться к великому!
Столкнулся с одной проблемой. Большой расход памяти. Любой выбранный объект остаётся в оперативной памяти, так как мы не знаем, нужен ли нам будет ещё объект или нет, мы его все равно храним… типа, а вдруг понадобится.
Угу, причем если вдруг понадобится, будь добр имей ссылку на этот объект, потому что выборка этих объектов повторно через ActiveRecord наплодит еще копий.
Как быть не знаешь? )
Я стараюсь сделать Service Layer, который как бы прослойка между контроллерами и моделями, и стараюсь не вызывать апи ActiveRecord кроме как из Service Layer, а из контроллера вызываю что-то типа $articleService->getLastArticles(5). Если память начнет быстро уходить или запросы станут тяжелыми, я всегда могу внутри метода сервиса переписать джоин через активрекорд на чистый sql или сделать кеширование в переменную.

Именно по причине тяги к энтерпрайз паттернам меня смущают виджеты, которые лезут в базу (помоему это противоречит даже MVC) и наличие знаний о базе и о маппинге в самой модели.
А у вас Service Layer наружу отдает объекты СActiveRecord?
Мне тоже нравится идея промежуточного слоя, но вот модели с их богатым интерфейсом несколько портят картину. Разделить бы CActiveRecord на Domain Model (CModel) и Data Mapper (CActiveFinder + insert/update логика).
Стараюсь всякие файндеры и прочую «статику» держать в сервисе, а отдавать — объект CActiveRecord. В методы модели стараюсь выносить всякие простые вещи вроде $article->getTopComments().
Насчет Domain Model это конечно уже к симфони + doctrine. Правда вот во всем этом кроется то, что мне не нравится в стаке PHP — все решения, перетянутые с JPA или рельсы выглядят громоздко. Субъективно, конечно.
В Yii2 finder отдельно, модель отдельно.
Это хорошо. А насколько модель отдельно? Кто сохраняет изменения в БД, модель или finder? Модель знает что-нибудь про БД?
Если речь именно про AR, то сохраняет модель она сама. Отдельной сущности, занимающейся этим нет. Finder занимается запросами на выборку, критериями и т.д.

Модель через ActiveMetaData получает данные о структуре таблицы из БД и кеширует их. Обычный ActiveRecord.
Кстати, можете посмотреть как это сделано в Doctrine2. Там не создается лишних объектов и запросов. Может стоит предусмотреть возможность ее использования вместо AR?
Так возможность есть.
Sign up to leave a comment.

Articles