Nov 27 2008

Symfony: Propel предзагрузка объектов (preload objects)

Category: Symfonyingvar @ 22:33

Цель: уменьшить количество запросов для таблиц использующих много внешних ключей.

Описание проблемы: имеем 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

Tags: ,

4 Responses to “Symfony: Propel предзагрузка объектов (preload objects)”

  1. rpsblog.com » A week of symfony #100 (24-&gt;30 november 2008) says:

    […] Symfony: Propel ???????????? ???????? (preload objects) […]

  2. Symfony: Propel предзагрузка объектов (preload objects) says:

    […] Symfony: Propel предзагрузка объектов (preload objects) Теги: Propel […]

  3. Eugeniy says:

    Здесь описывается пример только для связи «один к одному». А вот как быть когда связь «один у многим»?
    т. е.
    propel:
    product:
    id: ~
    name: …
    product_image:
    id: ~
    product_id: ~
    file: …

    т. е. как подгрузить все объекты ProductImage.
    Основная загвоздка в том что Product->getProductImages() проверяет private аттрибут Product->lastProductImageCriteria

    у меня было немного кривое кривое решение которое работало в 1.1, но в 1.2 оно работать перестало. Причину искать слишком непросто )

  4. ingvar says:

    2 Eugeniy: да задачка чуть усложняется :). На пропели такие решения получаются слишком громоздкими.

Leave a Reply to Symfony: Propel предзагрузка объектов (preload objects)