こんにちは!フリーエンジニアのせきです。
CakePHPでは、モデルにモデル同士の関連を定義し、他のモデルのデータを一緒に取得したり、保存したりすることができます。
この記事では、
・hasManyとは何か知りたい
・hasManyの使い方を知りたい
・条件の指定、並び替え、外部キーの指定方法について知りたい
という基本的な内容から、
・関連するデータをまとめて保存する方法を知りたい
といった応用的な内容に関しても解説していきます。
今回はそんなモデル同士の関連を定義するhasManyについて、わかりやすく解説します!
hasManyとは
CakePHPでは、モデル同士の関連をアソシエーションといいます。
hasManyはアソシエーションのひとつであり、「1対多」の関連を表します。
例えば、1人のユーザは複数の趣味を持ちます。
この時、ユーザと趣味は「1対多」の関係になります。
hasManyの使い方
先ほどのユーザと趣味を例に解説していきます。
以下のようなテーブルを作成し、データを登録しておきます。
ユーザテーブルのテーブル定義
create table users ( id int not null auto_increment, -- ID name varchar(32), -- 名前 primary key (id) );
ユーザテーブルの初期データ
ID | 名前 |
---|---|
1 | 林 |
2 | 中田 |
3 | 木下 |
趣味テーブルのテーブル定義
create table user_hobbies ( id int not null auto_increment, -- ID user_id int not null, -- ユーザID hobby varchar(32), -- 趣味 enable boolean default true, -- 有効かどうか primary key (id) );
趣味テーブルの初期データ
ID | ユーザID | 趣味 | 有効かどうか |
---|---|---|---|
1 | 1 | スポーツ | true |
2 | 1 | 音楽 | false |
3 | 1 | 料理 | true |
4 | 2 | スポーツ | true |
5 | 2 | 映画 | true |
6 | 2 | プログラミング | false |
7 | 3 | 旅行 | true |
8 | 3 | 音楽 | true |
それぞれのModelを作成しておきます。
ユーザテーブルのModel
src\Model\Entity\User.php
<?php namespace App\Model\Entity; use Cake\ORM\Entity; class User extends Entity { }
src\Model\Table\UsersTable.php
<?php namespace App\Model\Table; use Cake\ORM\Table; class UsersTable extends Table { }
趣味テーブルのModel
src\Model\Entity\UserHobby.php
<?php namespace App\Model\Entity; use Cake\ORM\Entity; class UserHobby extends Entity { }
src\Model\Table\UserHobbiesTable.php
<?php namespace App\Model\Table; use Cake\ORM\Table; class UserHobbiesTable extends Table { }
基本的な使い方
アソシエーションは、Tableオブジェクトのinitialize()に以下のように定義します。
$this->hasMany('モデル名');
UsersTableに、initialize()を追加します。
public function initialize(array $config) { $this->hasMany('UserHobbies'); }
このように定義しておくと、UsersのControllerでcontainを使用し、UserHobbiesのデータを取得することができます。
bakeでUsersのControllerを作成します。
bin/cake bake controller users
bakeについては、以下の記事で詳しく解説しています。
作成された「src\Controller\UsersController.php」のindexメソッドを、以下のように修正します。
public function index() { $query = $this->Users->find('all')->contain(['UserHobbies']); $this->set('users', $query); }
「find(‘all’)」で全件検索し、「contain(‘UserHobbies’)」でUserHobbiesのデータも取得するように指定しています。
一覧表示のTemplateを作成します。
src\Template\Users\index.ctp
<table cellpadding="0" cellspacing="0"> <thead> <tr> <th>ID</th> <th>名前</th> <th>趣味</th> </tr> </thead> <tbody> <?php foreach ($users as $user): ?> <tr> <td><?= $user->id ?></td> <td><?= $user->name ?></td> <td> <?php foreach ($user->user_hobbies as $userHobby): ?> <?= $userHobby->hobby ?><br> <?php endforeach; ?> </td> </tr> <?php endforeach; ?> </tbody> </table>
UserHobbiesのデータは「$user->user_hobbies」で取得することができます。
表示してみます。
http://[サーバ名]/[プロジェクト名]/users/
UserHobbiesのデータもすべて表示されました。
条件を指定する
条件に当てはまるデータのみを同時に取得したい場合は、hasManyにsetConditionsを使用して条件を指定します。
UserHobbiesの有効なデータ(enableがtrue)のみを取得する場合は、TableオブジェクトhasManyの定義と同時に、以下のように記述します。
$this->hasMany('UserHobbies') ->setConditions([ 'UserHobbies.enable' => true ]);
配列を使用して記述することもできます。
$this->hasMany('UserHobbies', [ 'conditions' => ['UserHobbies.enable' => true] ]);
表示してみます。
UserHobbiesの有効なデータのみが表示されています。
並び替えをする
UserHobbiesのデータでソートしたい場合は、hasManyにsetSortを使用してソート順を指定します。
UserHobbiesのIDの降順でソートする場合は、TableオブジェクトhasManyの定義と同時に、以下のように記述します。
$this->hasMany('UserHobbies') ->setSort([ 'UserHobbies.id' => 'DESC' ]);
表示してみます。
UserHobbiesのIDの降順で表示されています。
外部キーを指定する
モデルを関連づけるための外部キーは、デフォルトでは「当該のモデル名(単数形)_id」が使用されます。
Usersでいうと、「user_id」がUserHobbiesとの外部キーになります。
この外部キーは、別の名前を指定することもできます。
「u_id」を外部キーの名前としたい場合は、TableオブジェクトのhasManyの定義と同時に、以下のように記述します。
$this->hasMany('UserHobbies') ->setForeignKey('u_id');
関連するデータをまとめて保存する
「1対多」の関係にあるモデルは、取得だけでなく保存もまとめて行うことができます。
保存するデータを入力する入力画面を作成します。
src\Template\Users\add.ctp
<div class="users form large-9 medium-8 columns content"> <?= $this->Form->create($user) ?> <fieldset> <?php echo $this->Form->control('name'); echo $this->Form->control('user_hobbies.0.hobby'); echo $this->Form->control('user_hobbies.1.hobby'); echo $this->Form->control('user_hobbies.2.hobby'); ?> </fieldset> <?= $this->Form->button(__('Submit')) ?> <?= $this->Form->end() ?> </div>
UserHobbiesのデータは「テーブル名(小文字).連番.項目名」をcontrolメソッドに指定します。
この例では、1人のユーザに3つ(0~2)の趣味が保存できるようにしてありますが、連番は必要な数だけ振ることができます。
フォームを作成する方法については、以下の記事で詳しく解説しています。
次に、bakeで作成したcontrollerのaddメソッドを修正します。
// 修正前 $user = $this->Users->patchEntity($user, $this->request->getData());
// 修正後 $user = $this->Users->patchEntity($user, $this->request->getData(), ['associated' => ['UserHobbies']]);
最後に「[‘associated’ => [‘UserHobbies’]]」を追加しています。
‘UserHobbies’の部分には、TableオブジェクトのhasManyに指定したのと同じものを指定します。
入力画面を表示します。
http://[サーバ名]/[プロジェクト名]/users/add
入力し保存してみます。
user_hobbiesテーブルにもデータが保存されました。
アソシエーションのbelongsTo
ここで紹介したアソシエーションのhasManyは「1対多」の関連を表します。
「多対1」の関連を表すには、アソシエーションのbelongsToを使用します。
belongsToについては、以下の記事で詳しく解説しています。
まとめ
今回はアソシエーションのひとつであるhasManyについて解説しました。
アソシエーションの種別をひとつずつ覚えて、使いこなせるようになりましょう。
hasManyについて忘れてしまったら、この記事を思い出して下さい!