在 CakePHP 中定义两个模型之间同时有多个和有一个关联的方法?
在 CakePHP 框架内,我长期以来遇到的问题是定义两个模型之间同时的 hasOne
和 hasMany
关系。例如:
BlogEntry hasMany Comment
BlogEntry hasOne MostRecentComment
(其中 MostRecentComment
是具有最新 created
字段的 Comment
)
在 BlogEntry 中定义这些关系模型属性有问题。 CakePHP 的 ORM 将“有一”关系实现为 INNER JOIN
,因此只要有多个 Comment,BlogEntry::find('all')
调用就会返回多个每个 BlogEntry 的结果。
我过去通过几种方式解决了这些情况:
使用模型回调(或者,有时,甚至在控制器或视图中!),我用以下方法模拟了 MostRecentComment:
$this->data['MostRecentComment'] = $this->data['Comment'][0];
比如说,如果我需要通过Comment.created
以外的任何方式来排序评论,这会很快变得丑陋。它也没有 Cake 的内置分页功能来按 MostRecentComment 字段排序(例如,按MostRecentComment.created
按时间顺序对 BlogEntry 结果进行排序。维护一个额外的外键
BlogEntry。 起来很烦人,并且破坏了 Cake 的 ORM:它的含义是
BlogEntry ownsTo MostRecentComment
。
。解决方案还有很多不足之处,所以前几天我坐下来解决这个问题,并研究了一个更好的解决方案,我在下面发布了我的最终解决方案,但我会很兴奋(也许只是一点点<。 /em> 感到羞愧)发现有一些令人兴奋的简单解决方案一直困扰着我,或者满足我的标准的任何其他解决方案:
- 它必须能够按
Model::find 中的 MostRecentComment 字段进行排序。
级别(即不仅仅是结果的按摩); - 它不应该需要
comments
或blog_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:
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 byComment.created
. It also doesn't Cake's in-built pagination features to sort by MostRecentComment fields (e.g. sort BlogEntry results reverse-chronologically byMostRecentComment.created
.Maintaining an additional foreign key,
BlogEntry.most_recent_comment_id
. This is annoying to maintain, and breaks Cake's ORM: the implication isBlogEntry 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
orblog_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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我开发的解决方案如下:
概念很简单。
_bindMostRecentComment
方法定义了一个相当标准的 has-one 关联,在关联条件中使用子查询来确保只有最新的 Comment 才会连接到 BlogEntry 查询。该方法本身在任何Model::find()
调用之前调用,可以对每个 BlogEntry 的 MostRecentComment 进行过滤或排序。我意识到可以在
hasOne
类成员中定义此关联,但我必须编写一堆原始 SQL,这让我犹豫不决。我更愿意从 BlogEntry 的构造函数中调用
_bindMostRecentComment
,但是(根据文档)使绑定永久化的 Model::bindModel() 参数似乎不起作用,因此绑定具有在 beforeFind 回调中完成。The solution I developed is the following:
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 anyModel::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.