Метод определения одновременных связей между двумя моделями в CakePHP? - PullRequest
1 голос
/ 24 марта 2010

Одна вещь, с которой у меня долгое время были проблемы, в рамках CakePHP, это определение одновременных отношений hasOne и hasMany между двумя моделями. Например:

BlogEntry hasMany Comment
BlogEntry hasOne MostRecentComment (где MostRecentComment - это Comment с самым последним полем created)

Определение этих отношений в свойствах модели BlogEntry проблематично. ORM в CakePHP реализует отношение has-one как INNER JOIN, поэтому, как только появляется более одного комментария, вызовы BlogEntry::find('all') возвращают несколько результатов на BlogEntry.

В прошлом я обходил эти ситуации несколькими способами:

  1. Используя обратный вызов модели (или, иногда, даже в контроллере или представлении!), Я смоделировал MostRecentComment с:
    $this->data['MostRecentComment'] = $this->data['Comment'][0];
    Это становится ужасно быстро, если, скажем, мне нужно заказать Комментарии любым способом, кроме как Comment.created. Кроме того, встроенные в Cake функции разбивки на страницы не позволяют сортировать по полям MostRecentComment (например, сортировать результаты BlogEntry в обратном хронологическом порядке по MostRecentComment.created.

  2. Ведение дополнительного внешнего ключа, BlogEntry.most_recent_comment_id. Это раздражает в обслуживании, и нарушает ORM Cake: значение BlogEntry belongsTo MostRecentComment. Это работает, но просто выглядит ... неправильно.

Эти решения оставляли желать лучшего, поэтому я сел с этой проблемой на днях и работал над лучшим решением. Я разместил свое возможное решение ниже, но я был бы рад (и, может быть, просто немного огорчен) обнаружить, что есть какое-то невероятно простое решение, которое ускользнуло от меня все это время. Или любое другое решение, которое соответствует моим критериям:

  • он должен иметь возможность сортировки по полям MostRecentComment на уровне Model::find (т. Е. Не только по массе результатов);
  • не требует дополнительных полей в таблицах comments или blog_entries;
  • он должен уважать «дух» CakePHP ORM.

(я также не уверен, что заголовок этого вопроса настолько краткий / информативный, насколько это возможно.)

1 Ответ

0 голосов
/ 24 марта 2010

Я разработал следующее решение:

class BlogEntry extends AppModel
{
    var $hasMany = array( 'Comment' );

    function beforeFind( $queryData )
    {
        $this->_bindMostRecentComment();

        return $queryData;
    }

    function _bindMostRecentComment()
    {
        if ( isset($this->hasOne['MostRecentComment'])) { return; }

        $dbo = $this->Comment->getDatasource();
        $subQuery = String::insert("`MostRecentComment`.`id` = (:q)", array(
            'q'=>$dbo->buildStatement(array(
                'fields' => array( String::insert(':sqInnerComment:eq.:sqid:eq', array('sq'=>$dbo->startQuote, 'eq'=>$dbo->endQuote))),
                'table'  => $dbo->fullTableName($this->Comment),
                'alias'  => 'InnerComment',
                'limit'  => 1,
                'order'  => array('InnerComment.created'=>'DESC'),
                'group'  => null,
                'conditions' => array(
                    'InnerComment.blog_entry_id = BlogEntry.id'
                )
            ), $this->Comment)
        ));

        $this->bindModel(array('hasOne'=>array(
            'MostRecentComment'=>array(
                'className' => 'Comment',
                'conditions' => array( $subQuery )
            )
        )),false);

        return;
    }

    // Other model stuff
}

Понятие простое. Метод _bindMostRecentComment определяет довольно стандартную ассоциацию has-one, используя подзапрос в условиях ассоциации, чтобы гарантировать, что только самые последние комментарии будут присоединены к запросам BlogEntry. Сам метод вызывается непосредственно перед любыми Model::find() вызовами, MostRecentComment каждого BlogEntry может быть отфильтрован или отсортирован по.

Я понимаю, что можно определить эту ассоциацию в члене класса hasOne, но мне пришлось бы написать несколько необработанных SQL, что заставляет меня задуматься.

Я бы предпочел вызвать _bindMostRecentComment из конструктора BlogEntry, но параметр Model :: bindModel (), который (согласно документации) делает перманент привязки, похоже, не работает, поэтому привязка должна быть сделано в обратном вызове beforeFind.

...