Цель: уменьшить количество запросов для таблиц использующих много внешних ключей.
Описание проблемы: имеем 4 таблицы: news, news_i18n, news_type и news_type_i18n. На странице нужно отобразить список из 20 новостей, используя все перечисленные таблицы. А это 21 запрос. Методы doSelectWithI18n и doSelectJoinAll не позволяют получить значения i18n NewsTypePeer для NewsPeer с минимальным количеством запросов к БД.
Решение проблемы: вместо 21 запроса к БД выполнить 2. Механизм — использовать предзагрузку объектов NewsTypePeer со значениями i18n.
Листинг 1: schema.yml
propel: _attributes: { package: lib.model.news } news_type: _attributes: { isI18N: true, i18nTable: news_type_i18n } id: ~ url_segment: { type: varchar, size: 255, required: true } news_type_i18n: title: { type: varchar, size: 255, required: true } news: _attributes: { isI18N: true, i18nTable: news_i18n } id: ~ news_type_id: { type: integer, default: 0, foreignTable: news_type, foreignReference: id } url_segment: { type: varchar, size: 255, required: true } is_published: { type: boolean, required: true, default: 0 } published_at: { type: timestamp, index: true } image: { type: varchar, size: 255, required: true } video: { type: varchar, size: 255, required: true } created_at: ~ news_i18n: title: { type: varchar, size: 255, required: true } announce: { type: varchar, size: 255, required: true } body: { type: longvarchar, required: true }
Листинг 2: метод preloadObjects для объекта News
<?php /** * * @author Igor Brovchenko webdev [at] tigor [dot] com [dot] ua * @package lib.model.news */ class NewsPeer extends BaseNewsPeer { /** * Preload Objects 1.0 * * @param string $class * @param array $objects * @param boolean $isI18n */ static function preloadObjects($class, &$objects, $isI18n = true) { if(!$objects) { return; } $peer = $class . 'Peer'; if($isI18n) { $resultObjects = call_user_func(array($peer, 'doSelectWithI18n'), new Criteria()); } else { $resultObjects = call_user_func(array($peer, 'doSelect'), new Criteria()); } // array of primary keys $pks = array(); // Remember primary key foreach($resultObjects as $key => $value) { $pks[$value->getPrimaryKey()] = $key; } foreach($objects as &$item) { $preloadObjectId = call_user_func(array($item, 'get' . $class . 'Id')); call_user_func(array($item, 'set' . $class), $resultObjects[$pks[$preloadObjectId]] ); } unset($resultObjects); unset($pks); } } // NewsPeer
Листинг 3: использование метода preloadObjects
$c = new Criteria(); $c->add(NewsI18nPeer::CULTURE, 'ru'); $news = NewsPeer::doSelectWithI18n($c); NewsPeer::preloadObjects('NewsType', $news, true); foreach($news as $value) { print $value->getId() . ' - ' .$value->getTitle() . ' : ' . $value->getNewsType()->getTitle() . "<br>"; }
Данный способ позволяет подгружать любые объекты с учетом локализации (i18n). В принципе ничего не мешает вынести этот метод из модели и использовать отдельно, например в хелпере. Один недостаток — это что выбираются все значения для объекта NewsType, т.е. если таблица маленькая, то проблем никаких нет. Можно сделать запрос, с условием выборки Criteria IN перечислив все getPrimaryKey.
Используемые материалы:
• Symfony plugin sfPropelActAsTaggableBehaviorPlugin
Декабрь 1st, 2008 at 05:15
[…] Symfony: Propel ???????????? ???????? (preload objects) […]
Декабрь 1st, 2008 at 16:47
[…] Symfony: Propel предзагрузка объектов (preload objects) Теги: Propel […]
Январь 25th, 2009 at 21:00
Здесь описывается пример только для связи «один к одному». А вот как быть когда связь «один у многим»?
т. е.
propel:
product:
id: ~
name: …
product_image:
id: ~
product_id: ~
file: …
т. е. как подгрузить все объекты ProductImage.
Основная загвоздка в том что Product->getProductImages() проверяет private аттрибут Product->lastProductImageCriteria
у меня было немного кривое кривое решение которое работало в 1.1, но в 1.2 оно работать перестало. Причину искать слишком непросто )
Январь 26th, 2009 at 15:13
2 Eugeniy: да задачка чуть усложняется :). На пропели такие решения получаются слишком громоздкими.