以下是用 Cake 1.3 测试的。
首先,您可能想要或已经在模型上为通常适用的所有其他情况定义 HABTM 关系:
class Post extends AppModel {
var $hasAndBelongsToMany = 'Tag';
}
class Tag extends AppModel {
var $hasAndBelongsToMany = 'Post';
}
根据 Cake 自己的文档:[ 1 ]
在 CakePHP 中,一些关联(belongsTo 和 hasOne)执行自动连接以检索数据,因此您可以发出查询以根据相关关联中的数据检索模型。
但是 hasMany 和 hasAndBelongsToMany 关联并非如此。这就是强制加入来拯救的地方。您只需定义必要的连接来组合表并获得查询所需的结果。
排除空的 HABTM 结果是这些时间之一。蛋糕书的同一部分解释了如何实现这一点,但我并没有从阅读文本中发现结果实现这一点过于明显。在 Cake Book 的示例中,他们使用 \ 连接路径 Book -> BooksTag -> Tags,而不是我们的 Tag -> PostsTag -> Posts。对于我们的示例,我们在 TagController 中进行如下设置:
$options['joins'] = array(
array(
'table' => 'posts_tags',
'alias' => 'PostsTag',
'type' => 'INNER',
'foreignKey' => false,
'conditions' => 'PostsTag.tag_id = Tag.id'
),
array(
'table' => 'posts',
'alias' => 'Post',
'type' => 'INNER',
'foreignKey' => false,
'conditions' => 'Post.id = PostsTag.post_id'
)
);
$tagsWithPosts = $this->Tag->find('all', $options);
确保将 foreignKey 设置为 false。这告诉 Cake 它不应该试图找出连接条件,而是只使用我们提供的条件。
由于连接的性质,这通常会带回重复的行。要减少返回的 SQL,请根据需要在字段上使用 DISTINCT。如果您想要 find('all') 通常返回的所有字段,这会增加您需要对每列进行硬编码的复杂性。(当然你的表结构不应该经常改变,但它可能会发生,或者如果你可能只有很多列)。要以编程方式获取所有列,请在 find 方法调用之前添加以下内容:
$options['fields'] = array('DISTINCT Tag.'
. implode(', Tag.', array_keys($this->Tag->_schema)));
// **See note
需要注意的是,HABTM 关系在主选择之后运行。本质上,Cake 获取了符合条件的标签列表,然后运行另一轮 SELECT 语句来获取相关的帖子;您可以从 SQL 转储中看到这一点。我们手动设置的“连接”适用于第一个选择,为我们提供所需的标签集。然后内置的 HABTM 将再次运行,为我们提供与这些标签相关的所有帖子。我们不会有任何没有帖子的标签,这是我们的目标,但如果添加了不属于我们任何初始“条件”的标签,我们可能会获得与该标签相关联的帖子。
例如,添加以下条件:
$options['conditions'] = 'Post.id = 1';
将产生以下结果:
Array (
[0] => Array (
[Tag] => Array (
[id] => 1
[name] => 'Tag1' )
[Post] => Array (
[0] => Array (
[id] => 1
[title] => 'Post1' )
[1] => Array (
[id] => 4
[title] => 'Post4' ) )
)
)
根据问题中的样本数据,只有 Tag1 与我们的“条件”语句相关联。所以这是“加入”返回的唯一结果。然而,由于 HABTM 在此之后运行,它抓取了与 Tag1 关联的所有帖子(Post1 和 Post4)。
快速提示 - 在 Model::find() 中执行 Ad-hoc Joins 中也讨论了这种使用显式连接来获取所需初始数据集的方法。本文还展示了如何推广该技术并将其添加到扩展 find() 的 AppModel。
如果我们真的也只想查看 Post1,我们需要添加一个 'contain'[ 2 ] 选项子句:
$this->Tag->Behaviors->attach('Containable');
$options['contain'] = 'Post.id = 1';
给出结果:
Array (
[0] => Array (
[Tag] => Array (
[id] => 1
[name] => 'Tag1' )
[Post] => Array (
[0] => Array (
[id] => 1
[title] => 'Post1' ) )
)
)
您可以使用 bindModel 来重新定义与 find() 实例的 HABTM 关系,而不是使用 Containable。在 bindModel 中,您将添加所需的 Post 条件:
$this->Tag->bindModel(array(
'hasAndBelongsToMany' => array(
'Post' => array('conditions' => 'Post.id = 1'))
)
);
我觉得对于那些试图了解蛋糕的自动魔法能力的初学者来说,使显式连接更容易看到和理解(我知道这是给我的)。另一种有效且可以说更“蛋糕”的方法是专门使用 unbindModel 和 bindModel 。http://nuts-and-bolts-of-cakephp.com上的Teknoid有一篇关于如何做到这一点的好文章:http: //nuts-and-bolts-of-cakephp.com/2008/07/17 /forcing-an-sql-join-in-cakephp/。此外,teknoid 将其变成了一种行为,您可以从 github 获取该行为:http: //nuts-and-bolts-of-cakephp.com/2009/09/26/habtamable-behavior/
** 这将按照数据库中定义的顺序拉取列。因此,如果没有首先定义主键,它可能不会按预期应用 DISTINCT。您可能需要修改它以使用 array_diff_key 从 $this->Model->primaryKey 中过滤掉主键。