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

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

CakePHPでは、モデルにモデル同士の関連を定義することができます。

この記事では、

・belongsToとは何か知りたい
・belongsToの使い方を知りたい
・外部キーや結合方法をカスタマイズする方法を知りたい

という基本的な内容から、

・階層の深いモデルのデータを取得する方法を知りたい

といった応用的な内容に関しても解説していきます。

今回はそんなモデル同士の関連を定義するbelongsToについて、わかりやすく解説します!

目次

belongsToとは

CakePHPでは、モデル同士の関連アソシエーションといいます。

belongsToはアソシエーションのひとつであり、「多対1」の関連を表します。

例えば、1つの部署には複数の従業員が所属します。

この時、従業員と部署は「多対1」の関係になります。

belongsToの使い方

先ほどの従業員と部署を例に解説していきます。

以下のようなテーブルを作成し、データを登録しておきます。

従業員テーブルのテーブル定義

create table employees
(
    id int not null auto_increment, -- ID
    section_id int, -- 部署ID
    employee_name varchar(32), -- 従業員名
    age int, -- 年齢
    primary key (id)
);

従業員テーブルの初期データ

ID部署ID従業員名年齢
12小林30
21丸山35
33大田27
44中村36
52木村40

部署テーブルのテーブル定義

create table sections
(
    id int not null auto_increment, -- ID
    office_id int, -- オフィスID
    section_name varchar(32), -- 部署名
    primary key (id)
);

部署テーブルの初期データ

IDオフィスID部署名
11営業
21開発
32人事

それぞれのModelを作成しておきます。

従業員テーブルのModel

src\Model\Entity\Employee.php

<?php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class Employee extends Entity
{
}

src\Model\Table\EmployeesTable.php

<?php
namespace App\Model\Table;

use Cake\ORM\Table;

class EmployeesTable extends Table
{
}

部署テーブルのModel

src\Model\Entity\Section.php

<?php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class Section extends Entity
{
}

src\Model\Table\SectionsTable.php

<?php
namespace App\Model\Table;

use Cake\ORM\Table;

class SectionsTable extends Table
{
}

基本的な使い方

アソシエーションは、Tableオブジェクトのinitialize()に以下のように定義します。

$this->belongsTo('モデル名');

EmployeesTableに、initialize()を追加します。

    public function initialize(array $config)
    {
        $this->belongsTo('Sections');
    }

このように定義しておくと、EmployeesのControllerでcontainを使用し、Sectionsのデータを取得することができます。

EmployeesのControllerです。

src\Controller\EmployeesController.php

<?php
namespace App\Controller;

use App\Controller\AppController;

class EmployeesController extends AppController
{
    public function index()
    {
        $query = $this->Employees->find('all')->contain('Sections')->order('Employees.id');
        $this->set('employees', $query);
    }
}

「find(‘all’)」で全件検索し、「contain(‘Sections’)」でSectionsのデータも取得するように指定しています。

「order(‘Employees.id’)」は従業員のID順でソートするための指定です。

以下のTemplateを作成し、表示してみます。

src\Template\Employees\index.ctp

<table>
    <thead>
        <tr>
            <th>ID</th>
            <th>部署名</th>
            <th>従業員名</th>
            <th>年齢</th>
        </tr>
    </thead>
    <tbody>
        <?php foreach ($employees as $employee): ?>
        <tr>
            <td><?= $employee->id ?></td>
            <td><?= $employee->section ? $employee->section->section_name : '-' ?></td>
            <td><?= $employee->employee_name ?></td>
            <td><?= $employee->age ?></td>
        </tr>
        <?php endforeach; ?>
    </tbody>
</table>

http://[サーバ名]/[プロジェクト名]/employees/
001

Sectionsのデータも取得できました。

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

外部キーを指定する方法

モデルを関連づけるための外部キーは、デフォルトでは「相手側のモデル名(単数形)_id」が使用されます。

Employeesでいうと、「section_id」がSectionsとの外部キーになります。

この外部キーは、別の名前を指定することもできます

「sec_id」を外部キーの名前としたい場合は、TableオブジェクトのbelongsToの定義と同時に、以下のように記述します。

$this->belongsTo('Sections')
    ->setForeignKey('sec_id');

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

$this->belongsTo('Sections',[
    'foreignKey' => 'sec_id'
]);

結合方法を指定する方法

belongsToでテーブルを結合した場合、結合方法は「LEFT JOIN」が使用されます。

INNER JOIN」を使用したい場合は、TableオブジェクトのbelongsToの定義と同時に、以下のように記述します。

$this->belongsTo('Sections')
    ->setJoinType('INNER');

「INNER JOIN」を指定して、一覧にアクセスしてみます。

002

従業員テーブルのID4のデータは、部署IDが4であり、部署テーブルには存在しないIDです。

そのため、「LEFT JOIN」の時には従業員ID4のデータが表示されていましたが、「INNER JOIN」では表示されません

階層の深いモデルのデータを取得する方法

部署テーブルと「多対1」の関連をもつテーブルを作成し、EmployeesのControllerからさらに階層の深いモデルのデータを取得してみます。

以下のようなオフィステーブルを作成します。

テーブル定義

create table offices
(
    id int not null auto_increment, -- ID
    office_name varchar(32), -- オフィス名
    primary key (id)
);

初期データ

IDオフィス名
1東京
2大阪

SectionsTableにbelongsToの定義を追加します。

    public function initialize(array $config)
    {
        $this->belongsTo('Offices');
    }

EmployeesControllerのcontainの部分を、以下のように記述します。

$query = $this->Employees->find('all')->contain(['Sections', 'Sections.Offices'])->order('Employees.id');

「.(ドット)」を使いモデル名を連結することで、さらに階層の深いモデルのデータも取得することができます。

Templateにオフィス名を追加し、表示してみます。

<table>
    <thead>
        <tr>
            <th>ID</th>
            <th>部署名</th>
            <th>オフィス</th>
            <th>従業員名</th>
            <th>年齢</th>
        </tr>
    </thead>
    <tbody>
        <?php foreach ($employees as $employee): ?>
        <tr>
            <td><?= $employee->id ?></td>
            <td><?= $employee->section ? $employee->section->section_name : '-' ?></td>
            <td><?= $employee->section ? $employee->section->office->office_name : '-' ?></td>
            <td><?= $employee->employee_name ?></td>
            <td><?= $employee->age ?></td>
        </tr>
        <?php endforeach; ?>
    </tbody>
</table>

003

Officesのデータも取得できました。

まとめ

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

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

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

この記事を書いた人

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

目次