こんにちは!フリーエンジニアのせきです。
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について忘れてしまったら、この記事を思い出して下さい!






