【CakePHP入門】アソシエーションのhasManyの使い方を理解しよう!

こんにちは!フリーエンジニアのせきです。

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趣味有効かどうか
11スポーツtrue
21音楽false
31料理true
42スポーツtrue
52映画true
62プログラミングfalse
73旅行true
83音楽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のデータを取得することができます。

bakeUsersの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/
001

UserHobbiesのデータもすべて表示されました。

条件を指定する

条件に当てはまるデータのみを同時に取得したい場合は、hasManyにsetConditionsを使用して条件を指定します。

UserHobbiesの有効なデータ(enableがtrue)のみを取得する場合は、TableオブジェクトhasManyの定義と同時に、以下のように記述します。

$this->hasMany('UserHobbies')
    ->setConditions([
        'UserHobbies.enable' => true
    ]);

配列を使用して記述することもできます。

$this->hasMany('UserHobbies', [
    'conditions' => ['UserHobbies.enable' => true]
]);

表示してみます。
002

UserHobbiesの有効なデータのみが表示されています。

並び替えをする

UserHobbiesのデータでソートしたい場合は、hasManyにsetSortを使用してソート順を指定します。

UserHobbiesのIDの降順でソートする場合は、TableオブジェクトhasManyの定義と同時に、以下のように記述します。

$this->hasMany('UserHobbies')
    ->setSort([
        'UserHobbies.id' => 'DESC'
    ]);

表示してみます。
003
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
004

入力し保存してみます。
005
user_hobbiesテーブルにもデータが保存されました。

アソシエーションのbelongsTo

ここで紹介したアソシエーションのhasManyは「1対多」の関連を表します。

多対1」の関連を表すには、アソシエーションのbelongsToを使用します。

belongsToについては、以下の記事で詳しく解説しています。

まとめ

今回はアソシエーションのひとつであるhasManyについて解説しました。

アソシエーションの種別をひとつずつ覚えて、使いこなせるようになりましょう。

hasManyについて忘れてしまったら、この記事を思い出して下さい!

この記事を書いた人

フリーランスでWebシステム開発やゲーム開発をしています。
読者の方にプログラミングの面白さをお伝えしたいです。

目次