CakePHPのアソシエーションについて触れる機会がありましたので、簡単ですがまとめてみます。
目次
はじめに
アソシエーションとはモデル同士のつながりのことです。
アソシエーションには4つの種類があります。
- hasOne
- hasMany
- belongsTo
- hasAndBelongsToMany
hasOne
モデルが別のモデルのデータを1件だけ所有するという関係です。
相手が外部キーをひとつだけもっています。
ユーザとプロフィールのテーブルを作成してみます。
※コードや実行結果はイメージです
CREATE TABLE users(
id int unsigned auto_increment primary key,
name varchar(30) NOT NULL,
);
CREATE TABLE profiles(
id int unsigned auto_increment primary key,
user_id int unsigned unique,
hobby varchar(256),
);
profilesテーブルの外部キーにuser_idが設定されています。
外部キーはほかのテーブルと関連付けるためフィールドです。
CakePHPでは User モデルと Profile モデルという2つのモデルがそれぞれのテーブルに連携されます。
1人のユーザはひとつのプロフィールを持つので、この2つのモデルの関係は1対1の関係になります。
この2つのモデルの関係をコードで書くと次のようになります。
class User extends AppModel {
public $hasOne = array('Profile');
}
このアソシエーションを定義して、$this->User->find()とfindすると、
Array
(
[User] => Array
(
[id] => 1
[nickname] => まーしー
)
[Profile] => Array
(
[id] => 1
[hobby] => music
)
);
User モデルだけでなく、Profile モデルの情報も同時に取得することができます。
このようにアソシエーションを利用すると、関連したモデルの情報も同時に取得することができます。
hasMany
相手のモデルに自分のIDが複数あるかもというもので、一対多の関係と呼ばれます。
ブログのサービスを例に考えます。
ユーザには複数の記事がひもづきますが、特定の記事は複数のユーザにひもづくことはなく1つの記事にひもづきます。
これが、1対多の関係です。
belongsTo
モデルが別のモデルのデータに所属しているという関係です。
先程のhasOneの項目でもあったプロフィールや、ブログの記事に対するコメントなど、データが所有者のIDを保持しているようなデータ構造です。
class Profile extends AppModel {
public $belongsTo = array('User');
}
プロフィールをfindすると、ユーザモデルの情報も取得します。
hasAndBelongsToMany
記事とタグの関係
先程は、hasManyの項目で考えたブログの記事とコメントの一対多の関係でした。
記事とタグ(Tag)の関係を考えます。
記事には複数のタグがつきます。
ここまでは、記事とコメントの関係と同じですが、記事Aに追加されたタグが、別の記事Bに追加されることもあるので、コメントとは違い、あるタグは複数の記事にひもづく可能性があります。
このような関係を多対多の関係と呼び、CakePHPではモデルで表現するために、hasAndBelongsToManyを使うことができます。
中間テーブル
このデータ構造を使うには2つのテーブルを中継する、中間テーブルを作成します。
テーブル名を2つのモデル名をアンダースコアでつないだ形にします。(post_tags)
中間テーブルにはそれぞれのテーブルのidに該当する項目を外部キーとして用意しておきます。
CREATE TABLE IF NOT EXISTS `post_tags` (
`id` int(10) unsigned NOT NULL,
`post_id` int(10) unsigned NOT NULL DEFAULT '0',
`tag_id` int(10) unsigned NOT NULL DEFAULT '0',
);
hasAndBelongsToManyを定義
Postモデル側
public $hasAndBelongsToMany = array(
'Tag' =>
array(
'className' => 'tag',
'joinTable' => 'post_tags',
'foreignKey' => 'post_id',
'associationForeignKey' => 'tag_id',
)
);
Tagモデル側
public $hasAndBelongsToMany = array(
'Post' =>
array(
'className' => 'post',
'joinTable' => 'post_tags',
'foreignKey' => 'tag_id',
'associationForeignKey' => 'post_id',
)
);
findしてみる
Post側から$this->Post->find()とすると、
array(
'Post' => array(
'id' => '1',
'title' => 'タイトルA',
'body' => '本文A',
),
'Tag' => array(
(int) 0 => array(
'id' => '1',
'tag' => 'タグ1',
'PostTag' => array(
'id' => '1',
'post_id' => '1',
'tag_id' => '1',
)
),
(int) 1 => array(
'id' => '2',
'tag' => 'タグ2',
'PostTag' => array(
'id' => '2',
'post_id' => '1',
'tag_id' => '2',
)
)
)
)
このような感じに、タイトルAの記事につけられたタグのデータも取得しています。
中間テーブル(PostTag)のデータも出力されていますね。
Tag側から$this->Tag->find()とすると、
array(
'Tag' => array(
'id' => '1',
'tag' => 'タグ1',
),
'Post' => array(
(int) 0 => array(
'id' => '1',
'title' => 'タイトルA',
'body' => '本文1',
'PostTag' => array(
'id' => '1',
'post_id' => '1',
'tag_id' => '1',
)
),
(int) 1 => array(
'id' => '2',
'title' => 'タイトルB',
'body' => '本文2',
'PostTag' => array(
'id' => '3',
'post_id' => '2',
'tag_id' => '1',
)
)
)
)
タグ1とタグ1がつけられた記事を取得しています。