読者です 読者をやめる 読者になる 読者になる

CakePHPでHABTMを使う際の「with」と「joinTable」について

概要

CakePHPのアソシエーションのなかで、「多対多」の関係を表すのがHABTM(hasAndBelongsToMany)です。HABTMは、他のアソシエーション(hasOne, belongsTo, hasMany)に比べて多少複雑なため正確に理解するのが難しく、CakePHPのフォーラム(http://cakephp.jp/)でもよく利用法に関する話題などが挙がっているようです。
これまで、なんとなく使い方は分かるものの、細部まで理解せずに使っていたのですが、ちょっと調べてみたところ非常にわかりやすい記事を見つけました。

この記事では、HABTMにおける「with」と「joinTable」の設定について詳しく解説されています。
元の記事は海外の記事なので、自分用のメモという意味も込めて重要な点を簡単にまとめてみたいと思います。

解説

以下では「with」、「joinTable」の設定を変更し、「auto-model名」、「結合テーブル名」をそれぞれ変更する際の注意点についてまとめたいと思います。なお、HABTMにおける「with」はCakePHP1.2から利用可能になった設定項目なので、対象バージョンはCakePHP1.2以降となります。

解説にあたっては、CakePHPのマニュアルでも使われているPost(記事)とTag(タグ)の関係を題材とします。各モデルの基本的な定義は以下の通りです。

<?php
class Post extends AppModel {
     var $name = 'Post';
     var $hasAndBelongsToMany = array('Tag');
}

class Tag extends AppModel {
     var $name = 'Tag';
     var $hasAndBelongsToMany = array('Post');
}
?>

解説に入る前に、まず「with」、「joinTable」について簡単な説明を以下に示します。

with:
 モデル間の関係を表すauto-modelの名前を設定します。今回の例の場合、デフォルトでは「PostsTag」となりますが、それ以外の名前を設定したい場合はここで設定します。

joinTable:
 結合テーブルの名前を設定します。今回の例の場合、デフォルトでは各モデル名の複数形をアンダーバーで繋いだ「posts_tags」となりますが、それ以外の名前を設定したい場合はここで設定します。

「joinTable」によって、結合テーブルの名前を変更する場合

結合テーブルの名前を変更したい場合、モデルの定義は以下のようになるかと思います。

<変更例1>
<?php
class Post extends AppModel {
     var $name = 'Post';
     var $hasAndBelongsToMany = array('Tag'=>array(
                                         'joinTable'=>'my_cool_join_table')
                );
}
?>


このように結合テーブルの名前を変更する際、注意すべき点が2点あります。

  1. joinTableの設定を変更すると、それに応じてauto-model名が書き換えられる
  2. 設定の変更はHABTMで関連する両方のモデルに適応しなくてはいけない。

まず1点目ですが、joinTableの設定を変更した場合、自動的にauto-model名も変更されます。今回の例の場合、joinTableの設定を「my_cool_join_table」に変更したので、auto-model名が「PostsTag」から「MyCoolJoinTable」に書き換えられます。このため、もしソースコード内で既に「PostsTag」を参照している部分があれば、これをすべて「MyCoolJoinTable」に変更するか、withに明示的に「PostsTag」を設定する必要があります。「with」の設定には何も変更を加えていないので、デフォルトのまま「PostsTag」を使えると考えてしまいそうですが、そうではないようです。

次に2点目ですが、上に挙げた<変更例1>ではPostモデルのjoinTableしか変更していません。HABTMは双方向の関係であるため、設定の変更は関連する両方のモデルに適応する必要があります(つい忘れがちです)。今回の例の場合は、TagモデルのjoinTableにも「my_cool_join_table」を設定する必要があります。

「with」によって、auto-modelの名前を変更する場合

auto-modelの名前を変更したい場合、モデルの定義は以下のようになるかと思います。

<変更例2>
<?php
class Post extends AppModel {
      var $name = 'Post';
      var $hasAndBelongsToMany = array('Tag'=>array('with'=>'PostTagJoin'));
}
?>

「with」の設定を変更し、auto-modelの名前を変更した際には、結合テーブル名には影響を及ぼしません。すなわち、<変更例2>の場合、結合テーブル名は「posts_tags_joins」ではなく、「posts_tags」のままです。
また、設定の変更をHABTMに関連する両方のモデルに適応する必要があるのは、「with」の場合も同様です。

まとめ

いくつか注意点について書きましたが、「joinTable」の設定を変更すると、自動的にauto-model名も変更されるというのは結構ハマリやすい点なのではないでしょうか。
うまく利用すれば非常に便利なHABTMなので、今後もさらに理解を深めるために色々と調べてみたいと思います。