今回はbelongsToに関して。多対一の結合で、正規化を実装するものだからおそらくはhasManyと並んで一番使うアソシエーションになるんじゃないかなぁって気がする。
基本はhasOneとほぼ同じ
belongsToは(LEFT)JOINでの結合を行う。これは、実はhasOneの結合方法と同じで、その動きも返される配列の形も殆ど変わらない。
両者の決定的な違いはbelongsToは外部キーを自身が持ち、hasOneは相手(アソシエーション先)が持つということみたい。
アソシエーション | 外部キー |
---|---|
hasOne | 相手が持つ |
belongsTo | 自分が持つ |
例えばBlogUser、Blogというモデルがあるとして、Blogに外部キーがある場合、以下の設定はほぼ同じ動きをする。
//Model/BlogUser.php <?php class BlogUser extends AppModel{ var $hasOne = 'Blog'; } ?> //上記のと以下の設定は動きがほぼ同じ //Model/Blog.php <?php class Blog extends AppModel{ var $belongsTo = 'BlogUser'; } ?>
なお当然ながら、belongsToを使う場合も自分のと結合テーブル両方のモデルを作成しておく必要がある。
belongsToの宣言
宣言も基本的にhasOneと変わらない。非常に便利なパラメーターがひとつあるだけ。
//app/Model/Blog.php <?php class Models extends AppModel{ public $hasOne = array( 'JoinTable'=>array( 'className' => 'JoinTable', 'foreignKey'=> 'model_id', 'type'=>'inner', 'conditions'=> array('JoinTable.id'=>1), 'fields' => null , 'order'=> null , 'dependent'=> true, 'counterCache' => true , 'counterScope' => null ) ); } ?>
■キー名(Blog)
この場合、エイリアス名となる。今回はBlogと書いてるけど、当然Bでも何でもいい。
■className
結合するモデル名。上のキー名がモデル名と一致してる場合は省略できる。
■foreignKey
結合先が持ってる外部キー。命名規約に沿ってる場合は省略可能。
■type
結合タイプをleft、right、inner、crossの中から選べる。
■conditions
find()で指定する以外に、範囲を制限する場合に使用する。
■fields
抽出するフィールドを選択する。全部を選択する場合、nullにするかこのキー自体を省略する。
逆に全部いらない場合はfalseと設定する。
■order
結合した際にソートする場合に指定する。
hasOneの場合は、find()時でも設定してる場合その後に着く。ASCと書くと昇順、DESCと書くと降順となる。
[補足]
あまりSQLに詳しくない人もいると思うので補足すると、前に着くか後に着くかは結構重要になる。
SQLの場合、ソートは記載された順から優先的にされる。
たとえば[BlogUser.id asc]が先に設定されていて、hasOneで[Blog.id desc]と設定した場合、最初にBlogUser.idで昇順ソートされ、BlogUser.idが同じ場合はBlog.idで降順ソートで返される。
■dependent
trueとすると、選択モデル(この場合はBlogUser)のフィールドが削除された時それと同じ外部キーを持つ結合モデルのフィールドも削除してくれる。
デフォルトはfalse。
便利なcounterCache機能
上記のまではhasOneと同じなんだけど、belongsToでは他にとても便利そうなcounterCacheという機能がサポートされてる。これを使うと、同じ外部キーIDを持つフィールドが何件あるかを相手モデル(アソシエーション先)に保存させることができる。
たとえばブログの投稿内容(Blog)とコメント(Comment)を関連付けさせる場合、コメント数が何件あるかをキャッシュさせることができるってことだね。
■使う前の準備
belongsToで結合するアソシエーション先(外部キーがない方)に、以下のようなフィールドを追加する。
選択モデル(アンダースコア型)_count (例)comment_count int(11) unsigned not null
あとはbelongsToでcounterCacheを設定するだけ。
■counterCache
trueとすると、上記の命名規約に沿ったフィールドに、追加、編集、削除の度に数値を増減させてくれる。
また、'counterCache'=>'フィールド名'と設定することもできる。
■counterScope
カウンターキャッシュを増減させる際、このパラメーターで条件を設定できる。
(counterCache版のconditionsみたいなものかな)
たとえば'counterScope'=>array('Model.field' => 1)というような感じで使う。
//commentsテーブルにblog_id(外部キー)がある //blogsテーブルにcomment_countがある class Comment extends AppModel{ public $belongsTo = array( 'Blog'=>array('counterCache' => true) ); }
これだけで、Commentモデルを操作(挿入/編集/削除)するとBlogモデル(アソシエーション先)の「comment_count」が増減するようになるみたい。
これに関して、色々なパターンで検証してみたいからまた別の機会で個別でまとめよかな。ひとまず、色々調べてる時に見つけた紹介されてるサイトをいくつか貼っておきます。
HappyQuality [CakePHP]counterCacheがすごく便利なのでメモ
WapBox [CakePHP]CakePHP - counterCacheについて
半年前の私への教科書 counterCache (HABTMでも)
Ks web Design 手動によるカウンターキャッシュの更新
省略して宣言する場合
belongsToでも、命名規約に沿ってたら当然省略して宣言できます。
あとbindModelももちろん使えるよ。
public $belongsTo = 'Model'; //または public $hasOne = array('Model1','Model2',...);
複数のテーブルを結合
belongsToは基本的に外部キーを自分が持ってるっていうだけで、あとは「cakePHP2.1でhasOneを使う」と殆ど同じなんで、そこを見てね。
面倒くさがりやさんはこちら
面倒臭がりやさんですね。もしかしたら友達になれるかもしれません。
ひとつのモデルに複数のモデルを同階層上にぶら下げる場合、単に配列として複数指定するだけで実装できる。もしもBlogUser→Blog→Contentというような順で結合したい場合、$recursive = 2というように設定をすることで実装できる。
//同じ階層の場合 var $belognsTo = array('Blog','Content'); //階層が違う場合 //BlogUser.php var $belongsTo = 'Blog'; var $recursive = 2; //Blog.php var $belongsTo = 'Content';
[補足]
$recursiveを使った場合、返される配列は階層2のモデルの結果は階層1の中に入り込む。また、どうも(LEFT)JOINは使わずにクエリの分割によって実装するみたい。それが嫌な場合は、ちょっと強引だけど以下のように無理矢理設定することもできる。
//階層が同じ時と同じような配列の返し方をする //BlogUser.php var $hasOne = array( 'Blog', 'Content' => array( 'conditions'=>array('Content.id = Blog.id') ) );
ただ、この結合方法でsaveAssociated()など、cakePHPのアソシエーション用メソッドがちゃんと動くかどうかは調べてないです。
まあ、要はこっち側はhasOneと殆ど同じような感覚で実装できるってことですね。
DBを扱う時、自分が外部キーを持ってる方のテーブルをメインで扱うことが多いから、hasOneよりはこっちの方がよく使うことになりそう。
次回はcakePHPで一番便利そうなhasManyを取り上げてみます。