3.7.6 関連: モデルを結びつける
The original text for this section has changed since it was translated. Please help resolve this difference. You can:
CakePHP の最も強力な機能の1つは、モデルによって提供されるリレーショナルマッピングを結びつける能力です。CakePHP では、モデル間の結びつきは関連を通して処理されます。
アプリケーション内で異なるオブジェクト間の関係を定義することは自然なことです。たとえば: recipe データベース内で、recipe は複数の reviews を持ち、reviews は1つの author を持ちます。また、author は複数の recipe を持ちます。このような関係を定義すると、直感的かつ強力な方法でデータにアクセスすることができます。
この章の目的は、設計の仕方や定義の仕方やCakePHP でモデル間の関連の使用方法を示すことです。
データは様々なところからやってきますが、ウェブアプリケーションで最も一般的なストレージはリレーショナルデータベースです。この章で扱う大半はリレーショナルデータベースを想定しています。
3.7.6.1 関連の形式
The original text for this section has changed since it was translated. Please help resolve this difference. You can:
CakePHP で使用する関連の形式は: hasOne, hasMany, belongsTo, hasAndBelongsToMany (HABTM) の4つです。
| 関係 | 関連の形式 | 例 |
|---|---|---|
| 1対1 | hasOne | user は1つの profile をもつ |
| 1対多 | hasMany | システムの User は複数の recipe をもつことができる |
| 多対1 | belongsTo | recipe は user に属する |
| 多対多 | hasAndBelongsToMany | Recipe は複数の tag をもち、かつ属する |
関連は、定義したい関連の名前がついたクラス変数を生成することで定義します。クラス変数は単純に文字列とすることもできますが、関連の設定を定義するために多次元配列にすることもできます。
<?php
class User extends AppModel {
var $name = 'User';
var $hasOne = 'Profile';
var $hasMany = array(
'Recipe' => array(
'className' => 'Recipe',
'conditions' => array('Recipe.approved' => '1'),
'order' => 'Recipe.created DESC'
)
);
}
?>
<?phpclass User extends AppModel {var $name = 'User';var $hasOne = 'Profile';var $hasMany = array('Recipe' => array('className' => 'Recipe','conditions' => array('Recipe.approved' => '1'),'order' => 'Recipe.created DESC'));}?>
上記の例では、'Recipe' という単語の最初のインスタンスが 'エイリアス(別名)' になります。これは関係で使用する識別子となり、これを選択するだけで様々なことが実行できます。通常は、クラス名と同じ名前を選択しますが。ただしエイリアスは、単独のモデル内の belongsTo/hasMany または belongsTo/hasOne の関連において、ユニークでなければなりません。モデルのエイリアスにユニークでない名前を選択すると、不具合が発生します。
3.7.6.2 hasOne
The original text for this section has changed since it was translated. Please help resolve this difference. You can:
では、User モデルを作成しましょう。このモデルは、Profile モデルと hasOne の関係があります。
まずはデータベーステーブルのキーが正確に設定されている必要があります。hasOne の関係が動作するには、あるテーブルは外部キーを持つ必要があり、そのキーはもう一方のレコードを指し示します。profile テーブルは、user_id というフィールドを持っています。基本的な形式は次のようになります:
| 関係 | スキーマ |
|---|---|
| Apple hasOne Banana | bananas.apple_id |
| User hasOne Profile | profiles.user_id |
| Doctor hasOne Mentor | mentors.doctor_id |
User モデルファイルを /app/models/user.php に保存します。‘User hasOne Profile’の関係を定義するために、モデルクラスに $hasOne プロパティを追加します。/app/models/profile.php 内に Profile モデルを指定することを忘れないでください。そうしないと関連は動作しません。
<?php
class User extends AppModel {
var $name = 'User';
var $hasOne = 'Profile';
}
?>
<?phpclass User extends AppModel {var $name = 'User';var $hasOne = 'Profile';}?>
モデルファイル内にこの関係を記述するには2つの方法があります。もっとも簡単な方法は、上記で指定したように関連モデルのクラス名を含む文字列を $hasOne プロパティにセットすることです。
より細かい制御が必要な場合、配列を使用して関連を定義します。たとえば、1つだけのレコードを含むように関連を限定したいというような場合です。
<?php
class User extends AppModel {
var $name = 'User';
var $hasOne = array(
'Profile' => array(
'className' => 'Profile',
'conditions' => array('Profile.published' => '1'),
'dependent' => true
)
);
}
?>
<?phpclass User extends AppModel {var $name = 'User';var $hasOne = array('Profile' => array('className' => 'Profile','conditions' => array('Profile.published' => '1'),'dependent' => true));}?>
hasOne 関連の配列で指定可能なキーは次の通りです:
- className: 現在のモデルに関連したモデルのクラス名。‘User hasOne Profile’ という関係を定義する場合、className キーは‘Profile’ になります。
- foreignKey: もう一方のモデルにある外部キーの名前。複数の hasOne 関係を定義する必要がある場合に特に便利です。このキーのデフォルト値は、現在のモデル名のアンダースコア区切りの単数形で、末尾に‘_id’をつけたものです。上記の例では、'user_id' となります。
- conditions: 関連モデルのレコードを限定するための SQL。SQL 内でモデル名を使用することを習慣にしておくようにしておきましょう:“approved = 1.” よりも、“Profile.approved = 1” の方が良い記述です。
- fields: 関連モデルのデータが取得された際に取り出すフィールドのリストです。デフォルトではすべてのフィールドを返します。
- dependent: dependent キーを true にセットし、delete() メソッドの cascade 引数を true をセットして呼び出すと、関連するモデルのレコードも併せて削除されます。この場合、true をセットしているので、User を削除すると、関連する Profile も削除します。
この関連を定義すると、User モデルの find は、関連した Profile モデルのレコードも(もし存在すれば)取り出します:
// $this->User->find() を呼び出した結果のサンプルArray
(
[User] => Array
(
[id] => 121
[name] => Gwoo the Kungwoo
[created] => 2007-05-01 10:31:01
)
[Profile] => Array
(
[id] => 12
[user_id] => 121
[skill] => Baking Cakes
[created] => 2007-05-01 10:31:01
)
)
3.7.6.3 belongsTo
The original text for this section has changed since it was translated. Please help resolve this difference. You can:
ここでは、User モデルから Profile のデータにアクセスします。User のデータに関連したデータにアクセスするために Profile モデルに belongsTo 関連を定義します。belongsTo 関連は、自然に hasOne や hasMany 関連の対になります: 他の方向からデータをみることができます。
データベースのテーブルに belongsTo 関係のためのキーを作成するには、次のような規則になります:
| 関係 | スキーマ |
|---|---|
| Banana belongsTo Apple | bananas.apple_id |
| Profile belongsTo User | profiles.user_id |
| Mentor belongsTo Doctor | mentors.doctor_id |
モデル(テーブル)が外部キーを持つ場合、そのモデルは他のモデル(テーブル)に属します。
次のような構文を使用して、/app/models/profile.php 内で Profile モデルに belongsTo 関連を定義することができます:
<?php
class Profile extends AppModel {
var $name = 'Profile';
var $belongsTo = 'User';
}
?>
<?phpclass Profile extends AppModel {var $name = 'Profile';var $belongsTo = 'User';}?>
配列を使用してより詳細な関係を定義することもできます。
<?php
class Profile extends AppModel {
var $name = 'Profile';
var $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
)
);
}
?>
<?phpclass Profile extends AppModel {var $name = 'Profile';var $belongsTo = array('User' => array('className' => 'User','foreignKey' => 'user_id'));}?>
belongsTo 関連の配列で有効なキーは以下のようになります:
- className: 現在のモデルに関連したモデルのクラス名。‘Profile belongsTo User’ という関係を定義する場合、className キーは‘User’になります。
- foreignKey: 現在のモデルにある外部キー名。複数の belongsTo 関係を定義する必要がある場合に、これは特に便利です。このキーのデフォルト値は、他のモデル名のアンダースコアで区切られた単数形で、末尾に‘_id’が付きます。
- conditions: 関連モデルのレコードを限定するために使用する SQL。SQL にモデル名を使用するのは良い習慣となります: “User.active = 1” は常に“active = 1”よりも推奨されます。
- fields: 関連モデルのデータを取得した際に取り出すフィールドのリスト。デフォルトではすべてのフィールドを返します。
- counterCache: (bool) true にセットすると、save() または delete() が呼び出されるたびに、関連モデルは自動的に外部テーブルの“[singular_model_name]_count”というフィールドをインクリメントまたはデクリメントします。カウンタフィールドの値は関連する行の番号を表します。
この関連が定義されると、Profile モデルの find 操作は、存在する場合は関連する User レコードを取得するでしょう:
//Sample results from a $this->Profile->find() call.
Array
(
[Profile] => Array
(
[id] => 12
[user_id] => 121
[skill] => Baking Cakes
[created] => 2007-05-01 10:31:01
)
[User] => Array
(
[id] => 121
[name] => Gwoo the Kungwoo
[created] => 2007-05-01 10:31:01
)
)
3.7.6.4 hasMany
The original text for this section has changed since it was translated. Please help resolve this difference. You can:
次のステップに進みましょう: “User hasMany Comment” という関連を定義します。 hasMany 関連を定義すると、User レコードを取得する際に、ユーザのコメントも取得できます。
データベースのテーブルに hasMany 関係のためのキーを作成するには、次のような規則で行います:
| 関係 | スキーマ |
|---|---|
| User hasMany Comment | Comment.user_id |
| Cake hasMany Virtue | Virtue.cake_id |
| Product hasMany Option | Option.product_id |
次のような構文を使用して、/app/models/user.php 内で User モデルに hasMany 関連を定義することができます。
<?php
class User extends AppModel {
var $name = 'User';
var $hasMany = 'Comment';
}
?>
<?phpclass User extends AppModel {var $name = 'User';var $hasMany = 'Comment';}?>
配列を使用してより詳細な関係を定義することもできます。
<?php
class User extends AppModel {
var $name = 'User';
var $hasMany = array(
'Comment' => array(
'className' => 'Comment',
'foreignKey' => 'user_id',
'conditions' => array('Comment.status' => '1'),
'order' => 'Comment.created DESC',
'limit' => '5',
'dependent'=> true
)
);
}
?>
<?phpclass User extends AppModel {var $name = 'User';var $hasMany = array('Comment' => array('className' => 'Comment','foreignKey' => 'user_id','conditions' => array('Comment.status' => '1'),'order' => 'Comment.created DESC','limit' => '5','dependent'=> true));}?>
hasMany 関連の配列で指定可能なキーは次の通りです:
- className: 現在のモデルに関連したモデルのクラス名。‘User hasMany Comment’ という関係を定義する場合、className キーは‘Comment’ になります。
- foreignKey: もう一方のモデルにある外部キーの名前。複数の hasMany 関係を定義する必要がある場合に特に便利です。このキーのデフォルト値は、現在のモデル名のアンダースコア区切りの単数形で、末尾に‘_id’をつけたものです。
- conditions: 関連モデルのレコードを限定するための SQL。SQL 内でモデル名を使用することを習慣にしておくようにしておきましょう:“status = 1.” よりも、“Comment.status = 1” の方が良い記述です。
- fields: 関連モデルのデータが取得された際に取り出すフィールドのリストです。デフォルトではすべてのフィールドを返します。
- order: 返される関連する行の並び順を定義する SQL。.
- limit: 返して欲しい関連する行の最大数。
- offset: 与えられた現在の条件と順番で関連したモデルのレコードを取り出す時に、スキップする行の数。
-
dependent: dependent キーが true にセットされると、再帰的なモデルの削除が可能になります。この例の場合、Comment レコードは、関連する User レコードが削除されたときに、同時に削除されます。
再帰的な削除をするためには、
Model->delete()メソッドの第2パラメータを true にセットしなければなりません。 - exclusive: exclusive が true にセットされると、再帰的なモデルの削除は、各エントリーを個別に削除するのではなく、deleteAll() の呼び出しによって削除されます。
- finderQuery: 関連モデルのレコードを取得するために CakePHP が使用できる完全な SQL。これは独自の結果が必要な場合に使用します。
この関連を定義すると、User モデルの find は、関連した Comment モデルのレコードも(もし存在すれば)取り出します:
// $this->User->find() を呼び出した結果のサンプルArray
(
[User] => Array
(
[id] => 121
[name] => Gwoo the Kungwoo
[created] => 2007-05-01 10:31:01
)
[Comment] => Array
(
[0] => Array
(
[id] => 123
[user_id] => 121
[title] => On Gwoo the Kungwoo
[body] => The Kungwooness is not so Gwooish
[created] => 2006-05-01 10:31:01
)
[1] => Array
(
[id] => 123
[user_id] => 121
[title] => More on Gwoo
[body] => But what of the ‘Nut? [created] => 2006-05-01 10:41:01
)
)
)
1つ覚えておいてほしいのは、両方の方向からデータを取得するためには、Comment が User と belongsTo 関連である必要があるということです。この章で述べてきたことは、User から Comment データを取得することができるということです。Comment モデル内で User と belongsTo 関連を追加すると、Comment モデルから User データを取得できます - 接続が完全であれば、どちらかのモデルからみても情報を流すことができます。
3.7.6.5 hasAndBelongsToMany (HABTM)
The original text for this section has changed since it was translated. Please help resolve this difference. You can:
さて、この時点で、すでに CakePHP におけるモデルの関連付けの専門家になっていることでしょう。すでに3つの関連に精通し、オブジェクトの関連付けの大半を学んできました。
それでは最後の関連である hasAndBelongsToMany もしくは HABTM に取り組みましょう。この関連が使用されるのは、2つのモデルがあり、それらがさまざまな方法で繰り返し何度も連携する必要がある場合です。
hasMany と HABTM の間の主な違いは、HABTM 内のモデル間の結びつきが排他的ではないということです。たとえば、HABTM を使用して Recipe モデルが Tag モデルと連携するとします。おばあちゃんのニョッキ(訳注:イタリアの伝統料理)レシピに "Italian" というタグを割り当てても、タグを「使い切る」ことにはなりません。蜂蜜でテカテカの BBQ スパゲッティにも、"Italian" とタグ付けできます。
hasMany 関連のオブジェクトの間の結びつきは排他的です。User が Comment と hasMany である場合、コメントは特定のユーザにのみ結び付けられます。あるユーザに結びついたコメントは、もう他のユーザに結びつけることはできません。
先に進めましょう。HABTM 関連を扱うには、追加のテーブルをデータベースにセットアップする必要があります。この新しい追加のテーブルの名前は、両方のモデルの名前が含まれており、それらをアルファベット順に並べてアンダースコア(「_」)で繋げたものにします。テーブルは少なくも2つのフィールドを含み、それぞれの外部キー(integer にすべき)が各モデルの主キーである必要があります。問題を避けるために、これら2つのフィールドを複合主キーにしないでください。アプリケーションにおいてそうする必要がある場合は、ユニークなインデックスを定義します。このテーブルに何か情報を追加する場合は、他のモデルと同じように簡単に扱えるよう、主キーのフィールド(規約上は「id」という名前のフィールド)を追加するとよいでしょう。
HABTM 両方のモデル名を含んだテーブルを追加する必要があります
| 関係 | スキーマ |
|---|---|
| Recipe HABTM Tag | id, recipes_tags.recipe_id, recipes_tags.tag_id |
| Cake HABTM Fan | id, cakes_fans.cake_id, cakes_fans.fan_id |
| Foo HABTM Bar | id, bars_foos.foo_id, bars_foos.bar_id |
テーブル名は規約によりアルファベット順です。
新しいテーブルが作成されると、モデルのファイルに HABTM 関連を定義できます。ここでは、文字列による定義ではなく、配列の構文を使いましょう:
<?php
class Recipe extends AppModel {
var $name = 'Recipe';
var $hasAndBelongsToMany = array(
'Tag' =>
array(
'className' => 'Tag',
'joinTable' => 'recipes_tags',
'with' => '',
'foreignKey' => 'recipe_id',
'associationForeignKey' => 'tag_id',
'unique' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
)
);
}
?>
<?phpclass Recipe extends AppModel {var $name = 'Recipe';var $hasAndBelongsToMany = array('Tag' =>array('className' => 'Tag','joinTable' => 'recipes_tags','with' => '','foreignKey' => 'recipe_id','associationForeignKey' => 'tag_id','unique' => true,'conditions' => '','fields' => '','order' => '','limit' => '','offset' => '','finderQuery' => '','deleteQuery' => '','insertQuery' => ''));}?>
HABTM 関連の配列で指定可能なキーは次の通りです:
- className: 現在のモデルに関連したモデルのクラス名。‘Recipe HABTM Tag’ という関係を定義する場合、className キーは‘Tag’ になります。
- joinTable: 関連で使用する結合テーブルの名前(もし HBTM の結合テーブル名が規約に従っていない場合)
- with: 結合テーブルのモデル名の定義。デフォルトでは CakePHP は自動的にモデルを生成します。上述の例だと、RecipesTag が呼び出されます。デフォルトの名前を上書きするために、このキーを使います。結合テーブルのモデルは、あらゆる「通常の」モデルのように、結合テーブルへアクセスするために使用することができます。
- foreignKey: 現在のモデルにある外部キーの名前。複数の HABTM 関係を定義する必要がある場合に特に便利です。このキーのデフォルト値は、現在のモデル名のアンダースコア区切りの単数形で、末尾に‘_id’をつけたものです。
- associationForeignKey: もう一方のモデルにある外部キーの名前。複数の HABTM 関係を定義する必要がある場合に特に便利です。このキーのデフォルト値は、もう一方のモデル名のアンダースコア区切りの単数形で、末尾に‘_id’をつけたものです。
- unique: もし true(デフォルト)なら、Cake は更新を行う際、外部キーのテーブルに新たなレコードを挿入する前に既存の関連レコードを削除します。したがって、更新を行う際には、既存の関連するレコードをもう一度渡す必要があります。
- conditions: 関連モデルのレコードを限定するための SQL。SQL 内でモデル名を使用することを習慣にしておくようにしておきましょう:“status = 1.” よりも、“Comment.status = 1” の方が良い記述です。
- fields: 関連モデルのデータが取得された際に取り出すフィールドのリストです。デフォルトではすべてのフィールドを返します。
- order: 返される関連する行の並び順を定義する SQL。.
- limit: 返して欲しい関連する行の最大数。
- offset: 与えられた現在の条件と順番で関連したモデルのレコードを取り出す時に、スキップする行の数。
- finderQuery, deleteQuery, insertQuery: 関連モデルのレコードを取得・削除・生成するために CakePHP が使用できる完全な SQL。これは独自の結果が必要な場合に使用します。
この関連を定義すると、Recipe モデルの find は、関連した Tag モデルのレコードも(もし存在すれば)取り出します:
// $this->Recipe->find() を呼び出した結果のサンプルArray
(
[Recipe] => Array
(
[id] => 2745
[name] => Chocolate Frosted Sugar Bombs
[created] => 2007-05-01 10:31:01
[user_id] => 2346
)
[Tag] => Array
(
[0] => Array
(
[id] => 123
[name] => Breakfast
)
[1] => Array
(
[id] => 124
[name] => Dessert
)
[2] => Array
(
[id] => 125
[name] => Heart Disease
)
)
)
Tag モデルを使用する際に Recipe データを取得したい場合は、Tag モデルに HABTM の関連を定義することを覚えておいてください。
HABTM 関連に基づいた独自の find クエリを実行することもできます。次の例をみてください:
上記の例と同じ構造(Recipe HABTM Tag)を仮定し、'Dessert' タグをもつすべての Recipe を取得したいとします。これを達成できる一つの方法(ただし悪い方法)は、アソシエーションそのものに検索する条件を適用することです:
$this->Recipe->bindModel(array(
'hasAndBelongsToMany' => array(
'Tag' => array('conditions'=>array('Tag.name'=>'Dessert'))
)));
$this->Recipe->find('all');
$this->Recipe->bindModel(array('hasAndBelongsToMany' => array('Tag' => array('conditions'=>array('Tag.name'=>'Dessert')))));$this->Recipe->find('all');
//Data Returned
Array
(
0 => Array
{
[Recipe] => Array
(
[id] => 2745
[name] => Chocolate Frosted Sugar Bombs
[created] => 2007-05-01 10:31:01
[user_id] => 2346
)
[Tag] => Array
(
[0] => Array
(
[id] => 124
[name] => Dessert
)
)
)
1 => Array
{
[Recipe] => Array
(
[id] => 2745
[name] => Crab Cakes
[created] => 2008-05-01 10:31:01
[user_id] => 2349
)
[Tag] => Array
(
}
}
}
この例では "Dessert" タグがついた全てのレシピしか返せないことに注意してください。これをきちんと達成するには、いくつかの方法があります。一つの方法は、Recipe ではなく Tag モデルを検索し、関連付いた Recipe も全て取得する方法です。
$this->Recipe->Tag->find('all', array('conditions'=>array('Tag.name'=>'Dessert')));
$this->Recipe->Tag->find('all', array('conditions'=>array('Tag.name'=>'Dessert')));
与えられた ID を検索するために、CakePHP が提供する結合テーブルのモデルを使うことも出来ます。
$this->Recipe->bindModel(array('hasOne' => array('RecipesTag')));
$this->Recipe->find('all', array(
'fields' => array('Recipe.*'),
'conditions'=>array('RecipesTag.tag_id'=>124) // id of Dessert
));
$this->Recipe->bindModel(array('hasOne' => array('RecipesTag')));$this->Recipe->find('all', array('fields' => array('Recipe.*'),'conditions'=>array('RecipesTag.tag_id'=>124) // id of Dessert));
フィルタリングを行うために必要な数の結合を生成するために、風変わりな関連付けを作成することもできます。例を見てください:
$this->Recipe->bindModel(array(
'hasOne' => array(
'RecipesTag',
'FilterTag' => array(
'className' => 'Tag',
'foreignKey' => false,
'conditions' => array('FilterTag.id = RecipesTag.tag_id')
))));
$this->Recipe->find('all', array(
'fields' => array('Recipe.*'),
'conditions'=>array('FilterTag.name'=>'Dessert')
));
$this->Recipe->bindModel(array('hasOne' => array('RecipesTag','FilterTag' => array('className' => 'Tag','foreignKey' => false,'conditions' => array('FilterTag.id = RecipesTag.tag_id')))));$this->Recipe->find('all', array('fields' => array('Recipe.*'),'conditions'=>array('FilterTag.name'=>'Dessert')));
両方の例は、次のデータを返します:
//Data Returned
Array
(
0 => Array
{
[Recipe] => Array
(
[id] => 2745
[name] => Chocolate Frosted Sugar Bombs
[created] => 2007-05-01 10:31:01
[user_id] => 2346
)
[Tag] => Array
(
[0] => Array
(
[id] => 123
[name] => Breakfast
)
[1] => Array
(
[id] => 124
[name] => Dessert
)
[2] => Array
(
[id] => 125
[name] => Heart Disease
)
)
}
同じようなバインディングトリックで、 HABTM モデルのページ付けを簡単にすることができます。一点だけ注意が必要です。ページ付けで2つのクエリ(レコードの数を数えるものと、実際のデータを取得するもの)が必要な場合、必ず bindModel(); の false パラメータをセットしてください。こうすることで、デフォルトのビヘイビアのように、単一ではなく複数のクエリにまたがってモデルの関連が維持されます。詳細は API に関する文書を参照してください。
アソシエーションをその場で取り扱うことについての詳しい情報は、その場でアソシエーションを生成、廃棄の章を参照してください。
その時々の目的に応じて、これらのテクニックを組み合わせたり適用してください。
3.7.6.6 その場でアソシエーションを生成、廃棄
The original text for this section has changed since it was translated. Please help resolve this difference. You can:
その場でモデルのアソシエーションを生成したり廃棄したりする必要がときどきあります。例えば、次のような理由があります:
- 取ってくる関連データを減らしたいが、すべてのアソシエーションが第一レベルのrecursionで設定されている。
- 関連データをソートしたりフィルタリングしたりするために、アソシエーションの設定を変えたい。
このアソシエーションの生成や廃棄は、CakePHP のbindModel()やunbindModel()などのモデルメソッドを使って実現できます。("Containable" という非常に便利なビヘイビアもあります。詳細は組み込みのビヘイビアについてのマニュアルを参照してください)では、モデルを設定してみて、bindModel() や unbindModel() がどのように動作するかを見てみましょう。2つのモデルで始めます:
<?php
class Leader extends AppModel {
var $name = 'Leader';
var $hasMany = array(
'Follower' => array(
'className' => 'Follower',
'order' => 'Follower.rank'
)
);
}
?>
<?php
class Follower extends AppModel {
var $name = 'Follower';
}
?>
<?phpclass Leader extends AppModel {var $name = 'Leader';var $hasMany = array('Follower' => array('className' => 'Follower','order' => 'Follower.rank'));}?><?phpclass Follower extends AppModel {var $name = 'Follower';}?>
LeadersController では、Leader やそれに関連する Follower を取得するために、Leader モデル内の find() メソッドを使用できます。上記に示したように、Leader モデル内の 関連配列には "Leader hasMany Followers" という関係を定義しています。デモとして、コントローラのアクション内でその関連を廃棄するために unbindModel() を使用してみましょう。
function someAction() {
// これは Leader を取得して、Follower も取得します
$this->Leader->findAll();
// hasMany を削除しましょう
$this->Leader->unbindModel(
array('hasMany' => array('Follower'))
);
// ここで find 関数を使用すると、
// Leaders を返しますが、Follower は返しません。
$this->Leader->findAll();
// 注意: unbindModel はすぐ次の find 関数にのみ影響します。
// その次の find 呼び出しは設定済みの関連情報を使用して
// 呼び出されます。
// unbindModel() の後にすでに findAll() を使用してしまったので、
// ここでは Leader とともに関連する Follower も取得されます。
$this->Leader->findAll();
}
function someAction() {// これは Leader を取得して、Follower も取得します$this->Leader->findAll();// hasMany を削除しましょう$this->Leader->unbindModel(array('hasMany' => array('Follower')));// ここで find 関数を使用すると、// Leaders を返しますが、Follower は返しません。$this->Leader->findAll();// 注意: unbindModel はすぐ次の find 関数にのみ影響します。// その次の find 呼び出しは設定済みの関連情報を使用して// 呼び出されます。// unbindModel() の後にすでに findAll() を使用してしまったので、// ここでは Leader とともに関連する Follower も取得されます。$this->Leader->findAll();}
もう1点。第2引数に false をセットしない限り、bindModel() や unbindModel() を使用した関連の削除や追加は、 次の モデル操作のみに作用します。第2引数が false にセットされると、bind は指定されたままの状態になります。次に unbindModel() の基本的な使用方法のパターンを示します:
$this->Model->unbindModel(
array('associationType' => array('associatedModelClassName'))
);
$this->Model->unbindModel(array('associationType' => array('associatedModelClassName')));
その場でアソシエーションを削除できたので、今度は追加してみましょう。まだ何も設定されていない Leader には Principle("指導方針") を関連づけないといけません。Principle モデルのモデルファイルは、変数 $name 以外の設定は書き込まれていません。その場で Leader に Principleモデルを関連付けてみましょう。(しかし次の find 操作にのみ影響することを忘れないでください)この関数は LeadersController 内にあります:
function anotherAction() {
// leader.php モデルファイル内には
// Leader hasMany Principle がないのでここでは
// Leader のみ取得します。
$this->Leader->findAll();
// bindModel() を使用して Leader モデルに新しい関連を
// 追加しましょう:
$this->Leader->bindModel(
array('hasMany' => array(
'Principle' => array(
'className' => 'Principle'
)
)
)
);
// 正しく関連付けされたので
// 1回の find 関数で Leader を取得すると
// 関連する Principle も取得されます:
$this->Leader->findAll();
}
function anotherAction() {// leader.php モデルファイル内には// Leader hasMany Principle がないのでここでは// Leader のみ取得します。$this->Leader->findAll();// bindModel() を使用して Leader モデルに新しい関連を// 追加しましょう:$this->Leader->bindModel(array('hasMany' => array('Principle' => array('className' => 'Principle'))));// 正しく関連付けされたので// 1回の find 関数で Leader を取得すると// 関連する Principle も取得されます:$this->Leader->findAll();}
bindModel() の基本的な使い方は、通常の関連配列と同じで、キーは作成しようとしている関連の種類の後に記述します:
$this->Model->bindModel(
array('associationName' => array(
'associatedModelClassName' => array(
// 通常の関連のキーをここに記述します
)
)
)
);
$this->Model->bindModel(array('associationName' => array('associatedModelClassName' => array(// 通常の関連のキーをここに記述します))));
新しく結合されたモデルは、モデルファイル内に関連の定義は必要ありませんが、適切に新しい関連が動作するためには正しくキーを設定する必要があります。
3.7.6.7 Multiple relations to the same model
このセクションには保留されている変更があります. More information about translations
There are cases where a Model has more than one relation to another Model. For example you might have a Message model that has two relations to the User model. One relation to the user that sends a message, and a second to the user that receives the message. The messages table will have a field user_id, but also a field recipient_id. Now your Message model can look something like:
<?php
class Message extends AppModel {
var $name = 'Message';
var $belongsTo = array(
'Sender' => array(
'className' => 'User',
'foreignKey' => 'user_id'
),
'Recipient' => array(
'className' => 'User',
'foreignKey' => 'recipient_id'
)
);
}
?>
<?phpclass Message extends AppModel {var $name = 'Message';var $belongsTo = array('Sender' => array('className' => 'User','foreignKey' => 'user_id'),'Recipient' => array('className' => 'User','foreignKey' => 'recipient_id'));}?>
Recipient is an alias for the User model. Now let's see what the User model would look like.
<?php
class User extends AppModel {
var $name = 'User';
var $hasMany = array(
'MessageSent' => array(
'className' => 'Message',
'foreignKey' => 'user_id'
),
'MessageReceived' => array(
'className' => 'Message',
'foreignKey' => 'recipient_id'
)
);
}
?>
<?phpclass User extends AppModel {var $name = 'User';var $hasMany = array('MessageSent' => array('className' => 'Message','foreignKey' => 'user_id'),'MessageReceived' => array('className' => 'Message','foreignKey' => 'recipient_id'));}?>
3.7.6.8 Joining tables
このセクションには保留されている変更があります. More information about translations
In SQL you can combine related tables using the JOIN statement. This allows you to perform complex searches across multiples tables (i.e: search posts given several tags).
In CakePHP some associations (belongsTo and hasOne) performs automatic joins to retrieve data, so you can issue queries to retrieve models based on data in the related one.
But this is not the case with hasMany and hasAndBelongsToMany associations. Here is where forcing joins comes to the rescue. You only have to define the necessary joins to combine tables and get the desired results for your query.
To force a join between tables you need to use the "modern" syntax for Model::find(), adding a 'joins' key to the $options array. For example:
$options['joins'] = array(
array(
'table' => 'channels',
'alias' => 'Channel',
'type' => 'LEFT',
'conditions' => array(
'Channel.id = Item.channel_id',
)
)
);
$Item->find('all', $options);
$options['joins'] = array(array('table' => 'channels','alias' => 'Channel','type' => 'LEFT','conditions' => array('Channel.id = Item.channel_id',)));$Item->find('all', $options);
Note that the 'join' arrays are not keyed.
In the above example, a model called Item is left joined to the channels table. You can alias the table with the model name, so the retrieved data complies with the CakePHP data structure.
The keys that define the join are the following:
- table: The table for the join.
- alias: An alias to the table. The name of the model associated with the table is the best bet.
- type: The type of join: inner, left or right.
- conditions: The conditions to perform the join.
With joins, you could add conditions based on related model fields:
$options['joins'] = array(
array('table' => 'channels',
'alias' => 'Channel',
'type' => 'LEFT',
'conditions' => array(
'Channel.id = Item.channel_id',
)
)
);
$options['conditions'] = array(
'Channel.private' => 1
);
$pirvateItems = $Item->find('all', $options);
$options['joins'] = array(array('table' => 'channels','alias' => 'Channel','type' => 'LEFT','conditions' => array('Channel.id = Item.channel_id',)));$options['conditions'] = array('Channel.private' => 1);$pirvateItems = $Item->find('all', $options);
You could perform several joins as needed in hasBelongsToMany:
Suppose a Book hasAndBelongsToMany Tag association. This relation uses a books_tags table as join table, so you need to join the books table to the books_tags table, and this with the tags table:
$options['joins'] = array(
array('table' => 'books_tags',
'alias' => 'BooksTag',
'type' => 'inner',
'conditions' => array(
'Books.id = BooksTag.books_id'
)
),
array('table' => 'tags',
'alias' => 'Tag',
'type' => 'inner',
'conditions' => array(
'BooksTag.tag_id = Tag.id'
)
)
);
$options['conditions'] = array(
'Tag.tag' => 'Novel'
);
$books = $Book->find('all', $options);
$options['joins'] = array(array('table' => 'books_tags','alias' => 'BooksTag','type' => 'inner','conditions' => array('Books.id = BooksTag.books_id')),array('table' => 'tags','alias' => 'Tag','type' => 'inner','conditions' => array('BooksTag.tag_id = Tag.id')));$options['conditions'] = array('Tag.tag' => 'Novel');$books = $Book->find('all', $options);
Using joins with Containable behavior could lead to some SQL errors (duplicate tables), so you need to use the joins method as an alternative for Containable if your main goal is to perform searches based on related data. Containable is best suited to restricting the amount of related data brought by a find statement.

