1

在 CakePHP 框架中,我长期以来一直遇到问题的一件事是定义两个模型之间的同步hasOnehasMany关系。例如:

BlogEntry hasMany Comment
BlogEntry hasOne MostRecentComment(最近的字段MostRecentComment在哪里)Commentcreated

在 BlogEntry 模型属性中定义这些关系是有问题的。CakePHP 的 ORM 将 has-one 关系实现为INNER JOIN,因此只要有多个 Comment,BlogEntry::find('all')调用就会返回每个 BlogEntry 的多个结果。

过去我曾通过几种方式解决过这些情况:

  1. 使用模型回调(或者,有时,甚至在控制器或视图中!),我已经模拟了一个 MostRecentComment :
    $this->data['MostRecentComment'] = $this->data['Comment'][0];
    如果我需要以除 by 以外的任何方式订购 Comments,这会很快变得丑陋Comment.created。它也没有 Cake 的内置分页功能来按 MostRecentComment 字段排序(例如,将 BlogEntry 结果按MostRecentComment.created.

  2. 维护一个额外的外键,BlogEntry.most_recent_comment_id. 这很烦人,并且破坏了 Cake 的 ORM:含义是BlogEntry belongsTo MostRecentComment. 它有效,但只是看起来......错了。

这些解决方案还有很多不足之处,所以前几天我坐下来解决这个问题,并研究了一个更好的解决方案。我已经在下面发布了我的最终解决方案,但是我很高兴(也许只是有点羞愧)发现有一些令人难以置信的简单解决方案一直以来都在逃避我。或任何其他符合我标准的解决方案:

  • 它必须能够按该Model::find级别的 MostRecentComment 字段进行排序(即,不仅仅是结果的按摩);
  • 它不应该需要commentsorblog_entries表中的其他字段;
  • 它应该尊重 CakePHP ORM 的“精神”。

(我也不确定这个问题的标题是否尽可能简洁/信息丰富。)

4

1 回答 1

0

我开发的解决方案如下:

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 关联,在关联条件中使用子查询来确保只有最近的 Comment 被加入到 BlogEntry 查询中。该方法本身在任何调用之前被调用Model::find(),每个 BlogEntry 的 MostRecentComment 可以被过滤或排序。

我意识到可以在hasOne类成员中定义此关联,但我必须编写一堆原始 SQL,这让我暂停了。

我更喜欢_bindMostRecentComment从 BlogEntry 的构造函数中调用,但是(根据文档)使绑定永久化的 Model::bindModel() 参数似乎不起作用,因此必须在 beforeFind 回调中完成绑定。

于 2010-03-24T18:40:56.863 回答