Aug 13 2008

Symfony и Propel 1.3: реализация своих joins в методах doSelect*

Category: SymfonyIgor @ 01:12

Это решение проблемы для Symfony 1.1 и Propel 1.3, когда нужно из двух таблиц объединенных черех join получить все данные. Т.е. хотим выполнить такой запрос

SELECT news.NEWS_ID, news.CATEGORY_ID, news.TITLE, news.BODY, news.CREATED_AT, 
    news_category.CATEGORY_ID, news_category.NAME
FROM `news`
LEFT JOIN news_category ON (news.CATEGORY_ID=news_category.CATEGORY_ID)

А вот с этим проблема, если таблицы не связаны через внешний ключ (foreign key), вывести данные из второй таблицы news_category проблематично, но возможно. В основе материал: Applying custom joins in doSelect* method. Но для новой версии материал не соответствует действительности. Чтобы все заработало, следует сделать следущее.


Рассмотрим ситуацию на примере. Есть 2-е таблицы, допустим, новости и категории новостей. Связь между таблицами через поле category_id.
Структура таблиц:

# Table "news"
news_id
category_id
title
body
created_at

# Table "news_category"
category_id
name

Мы создали модели, формы, БД:

$ symfony propel:build-model
$ symfony propel:build-forms
$ symfony propel:build-sql

Задача: сделать запрос и вывести все данные. Допустим сделать следующее

$c = new Criteria();
NewsPeer::addSelectColumns($c);
NewsCategoryPeer::addSelectColumns($c);
$c->addJoin(NewsPeer::CATEGORY_ID, NewsCategoryPeer::CATEGORY_ID, Criteria::INNER_JOIN);
$news = CoreAclUserPeer::doSelect($c);

# Выводим заголовок для первой записи, все отлично, а как же быть с <b>news_category.NAME</b>?
echo $news[0]->getTitle();

РЕШЕНИЕ:
Нужно будет подправить несколько файлов, файлы в om и map не трогаем (напоминаю, что пример для версии симфони 1.1 и пропел 1.3):
• /lib/model/BaseNewsPeer.php
• /lib/model/BaseNews.php

BaseNewsPeer.php

# Добавляем атрибут и один метод
protected $newsCategory = null;

  public static function doSelectJoinGroup(Criteria $c, $con = null, $join_behavior = Criteria::LEFT_JOIN)
  {
    $c = clone $c;

    // Set the correct dbName if it has not been overridden
    if ($c->getDbName() == Propel::getDefaultDB()) {
      $c->setDbName(self::DATABASE_NAME);
    }

    NewsPeer::addSelectColumns($c);
    $startcol = (NewsPeer::NUM_COLUMNS - NewsPeer::NUM_LAZY_LOAD_COLUMNS);
    NewsCategoryPeer::addSelectColumns($c);

    $c->addJoin(array(NewsPeer::CATEGORY_ID,), array(NewsCategoryPeer::CATEGORY_ID,), $join_behavior);
    $stmt = BasePeer::doSelect($c, $con);
    $results = array();

    while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
      // Hydrate the News object
      $omClass = NewsPeer::getOMClass();

      $cls = substr('.'.$omClass, strrpos('.'.$omClass, '.') + 1);
      $obj1 = new $cls();
      $obj1->hydrate($row);

      // Hydrate the NewsCategory object
      $omClass = NewsCategoryPeer::getOMClass();

      $cls = substr('.'.$omClass, strrpos('.'.$omClass, '.') + 1);
      $obj2 = new $cls();
      $obj2->hydrate($row, $startcol);

      // Add the $obj2 (NewsCategory) to $obj1 (News)
      $obj1->newsCategory = $obj2;

      $results[] = $obj1;
    }

     return $results;
  }

BaseNews.php

# добавляем метод
  public function getNewsCategory()
  {
    if(!$this->newsCategory)
    {
      $this->newsCategory = NewsCategoryPeer::retrieveByPk($this->getNewsId());
    }

    return $this->newsCategory;
  }

После это переписываем наш запрос

$c = new Criteria();
$this->userList = NewsPeer::doSelectJoinGroup($c);

# Выводим заголовок для первой записи
echo $news[0]->getTitle();

# Выводим название категории
echo $news[0]->getNewsCategory()->getName();

P.S.
Задача решена, к БД был выполнен только один запрос.
Протестировано на Symfony 1.1.2-DEV.

Tags: ,

4 Responses to “Symfony и Propel 1.3: реализация своих joins в методах doSelect*”

  1. solenko says:

    Одного не пойму. Зачем
    $c = clone $c;
    ???

  2. Igor says:

    Мне тоже это не понятно немного. Вот как у них написано в комментах:

    // we’re going to modify criteria, so copy it first
    $criteria = clone $criteria;

  3. ilya says:

    для того, чтобы вызов «doSelectJoinGroup» не менял передаваемую критерию (так как она может использоваться еще и для нахождения doCountXXX)

  4. ilya says:

    очень понравился этот фрагмент кода:
    $c->addJoin(array(NewsPeer::CATEGORY_ID,), array(NewsCategoryPeer::CATEGORY_ID,)

    появилась возможность делать запросы для выборки языков, например:
    $c->addJoin(array(LocalePeer::ID, LocaleI18nPeer::CULTURE), array(LocaleI18nPeer::LOCALE_ID, "'ru'"), Criteria::LEFT_JOIN);
    SELECT .. FROM locale LEFT JOIN locale_i18n ON (locale.id = locale_i18n.locale_id AND locale_i18n.culture = 'ru')

Leave a Reply