[CakePHP] 多対多のリレーション定義でのHABTMとhasMany throughの使い分け

CakePHPの4つのアソシエーションのうち、hasOne(1対1)、hasMany(1対多)、belongsTo(多対1)の3つに関しては理解が容易。だけど初心者としては、残りの1つ、hasAndBelongsToMany(HABTM)(多対多)のアソシエーションに関しては更なる理解が必要だなと思ったので、少し突っ込んで調べてみる。

HABTMの特徴

まずはクックブックを読んでHABTMの特徴を頭に入れる。

「このアソシエーションは、結合される2つのモデルがある場合に使われます。」
言い換えると、2つのテーブルを結合するために使われるということ。

「hasManyとHABTMの大きな違いはHABTMモデル間のリンクは排他的ではない、ということです。」
レシピと材料のテーブルがある場合、hasManyだと、あるレシピで使われている材料は他のレシピには使えない(排他的)けど、HABTMだと、材料が既にあるレシピで使われていても他のレシピでも使える(排他的ではない)ということ。

アソシエーション: モデル同士を繋ぐ — CakePHP Cookbook 2.x ドキュメント
http://book.cakephp.org/2.0/ja/models/associations-linking-models-together.html

HABTMとhasMany throughの使い分け

クックブックによると、多対多のリレーションを定義できるのはHABTMに限らない。

多対多のリレーションを定義するには2つの方法がある。

  • hasAndBelongToMeny (HABTM)
  • hasMany through

その使い分けの基準については、以下のページが参考になる。

“HABTMの中間モデルの利用について” フォーラム – CakePHP Users in Japan
http://cakephp.jp/modules/newbb/viewtopic.php?topic_id=2819&forum=6

上記仮定において話を進めます。A, BはCの中間テーブルを元にしたhabtmな関係ですが、今回のようにCに中間データ以外の情報を載せるときには、$hasAndBelongsToManyを使用すると、用意されているひどく単純な処理から外れるのが難しいか、または細かいデータ取得の制御に向いていません。
これは重い足枷となります。よってhasMany throughという方法をとるのが一般的なプラクティスです。
詳細は http://book.cakephp.org/2.0/ja/models/associations-linking-models-together.html#hasmany-through を見てください。

クックブックにも以下の説明がある。

  • HABTMの中間テーブルは付加データを持つことをサポートしていない。
  • なぜなら、hasAndBelongsToManyアソシエーションはデータを一旦削除してから、そのあとでデータを保存するため。
  • そのため、新しいレコードが挿入されるとき、外部キーID以外の追加フィールドのデータが失われてしまう。

ただし、最近のバージョンでは改良されているみたいだけど。

バージョン 2.1 で変更.
unique に keepExisting を指定すれば、追加フィールドのデータを失うことなく保存できます。 unique キーについてはHABTM association arrays を参照してください。

ということで、とりあえず僕の結論としては、多対多のリレーション定義は以下のルールに従うことにする。

  • アソシエーションに付加データが無い場合はHABTM
  • アソシエーションに付加データがある場合はhasMany though