以下は、Cake 1.3 でテストされました。
まず、HABTM 関係が通常適用される他のすべての状況で、モデルに HABTM 関係を定義する必要があるか、既に行っています。
class Post extends AppModel {
var $hasAndBelongsToMany = 'Tag';
}
class Tag extends AppModel {
var $hasAndBelongsToMany = 'Post';
}
Cake自身のドキュメントによると:[ 1 ]
CakePHP では、いくつかの関連付け (belongsTo と hasOne) が自動結合を実行してデータを取得するため、クエリを発行して、関連するモデルのデータに基づいてモデルを取得できます。
しかし、これは hasMany および hasAndBelongsToMany 関連付けには当てはまりません。ここで、結合の強制が役に立ちます。テーブルを結合し、クエリの目的の結果を得るために必要な結合を定義するだけです。
空の HABTM 結果を除外することは、これらの時間の 1 つです。Cake Book の同じセクションで、これを達成する方法が説明されていますが、テキストを読んでも、結果がこれを達成していることはあまり明白ではありませんでした。Cake Book の例では、Tag -> PostsTag -> Posts の代わりに \ join path Book -> BooksTag -> Tags を使用しています。この例では、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() でのアドホック結合の実行でも説明しています。この記事では、この手法を一般化して、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' ) )
)
)
Containable を使用する代わりに bindModel を使用して、find() のこのインスタンスとの HABTM 関係を再定義できます。bindModel で、目的の Post 条件を追加します。
$this->Tag->bindModel(array(
'hasAndBelongsToMany' => array(
'Post' => array('conditions' => 'Post.id = 1'))
)
);
Cake の自動魔法の能力に頭を悩ませようとしている初心者にとっては、明示的な結合を行う方が見やすく、理解しやすいと思います (私にとってはそうでした)。これを行うためのもう 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 から主キーを除外する必要がある場合があります。