在 CakePHP 中定义两个模型之间同时有多个和有一个关联的方法?

发布于 2024-08-26 23:42:23 字数 1344 浏览 10 评论 0原文

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

BlogEntry hasMany Comment
BlogEntry hasOne MostRecentComment (其中 MostRecentComment 是具有最新 created 字段的 Comment

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

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

  1. 使用模型回调(或者,有时,甚至在控制器或视图中!),我用以下方法模拟了 MostRecentComment:
    $this->data['MostRecentComment'] = $this->data['Comment'][0];
    比如说,如果我需要通过 Comment.created 以外的任何方式来排序评论,这会很快变得丑陋。它也没有 Cake 的内置分页功能来按 MostRecentComment 字段排序(例如,按 MostRecentComment.created 按时间顺序对 BlogEntry 结果进行排序。

  2. 维护一个额外的外键 BlogEntry。 起来很烦人,并且破坏了 Cake 的 ORM:它的含义是 BlogEntry ownsTo MostRecentComment

。解决方案还有很多不足之处,所以前几天我坐下来解决这个问题,并研究了一个更好的解决方案,我在下面发布了我的最终解决方案,但我会很兴奋(也许只是一点点<。 /em> 感到羞愧)发现有一些令人兴奋的简单解决方案一直困扰着我,或者满足我的标准的任何其他解决方案:

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

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

One thing with which I have long had problems, within the CakePHP framework, is defining simultaneous hasOne and hasMany relationships between two models. For example:

BlogEntry hasMany Comment
BlogEntry hasOne MostRecentComment (where MostRecentComment is the Comment with the most recent created field)

Defining these relationships in the BlogEntry model properties is problematic. CakePHP's ORM implements a has-one relationship as an INNER JOIN, so as soon as there is more than one Comment, BlogEntry::find('all') calls return multiple results per BlogEntry.

I've worked around these situations in the past in a few ways:

  1. Using a model callback (or, sometimes, even in the controller or view!), I've simulated a MostRecentComment with:
    $this->data['MostRecentComment'] = $this->data['Comment'][0];
    This gets ugly fast if, say, I need to order the Comments any way other than by Comment.created. It also doesn't Cake's in-built pagination features to sort by MostRecentComment fields (e.g. sort BlogEntry results reverse-chronologically by MostRecentComment.created.

  2. Maintaining an additional foreign key, BlogEntry.most_recent_comment_id. This is annoying to maintain, and breaks Cake's ORM: the implication is BlogEntry belongsTo MostRecentComment. It works, but just looks...wrong.

These solutions left much to be desired, so I sat down with this problem the other day, and worked on a better solution. I've posted my eventual solution below, but I'd be thrilled (and maybe just a little mortified) to discover there is some mind-blowingly simple solution that has escaped me this whole time. Or any other solution that meets my criteria:

  • it must be able to sort by MostRecentComment fields at the Model::find level (ie. not just a massage of the results);
  • it shouldn't require additional fields in the comments or blog_entries tables;
  • it should respect the 'spirit' of the CakePHP ORM.

(I'm also not sure the title of this question is as concise/informative as it could be.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

给我一枪 2024-09-02 23:42:23

我开发的解决方案如下:

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,这让我犹豫不决。

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

The solution I developed is the following:

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
}

The notion is simple. The _bindMostRecentComment method defines a fairly standard has-one association, using a sub-query in the association conditions to ensure only the most-recent Comment is joined to BlogEntry queries. The method itself is invoked just before any Model::find() calls, the MostRecentComment of each BlogEntry can be filtered or sorted against.

I realise it's possible to define this association in the hasOne class member, but I'd have to write a bunch of raw SQL, which gives me pause.

I'd have preferred to call _bindMostRecentComment from the BlogEntry's constructor, but the Model::bindModel() param that (per the documentation) makes a binding permanent doesn't appear to work, so the binding has to be done in the beforeFind callback.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文