中間テーブルの基本と使い方 〜多対多リレーションシップ〜  【CakePHP4】

中間テーブルの利用シーン

中間テーブルを利用するのは、「多対多リレーションシップ」を実装する場合です。

類似の概念として「1対多リレーションシップ」があります。

まずは、両者の違いを解説します。

1対多リレーションシップ:中間テーブル不要

1対多の関係として、以下の事例を挙げます。

「会社(Company)」と「社員(Worker)」の関係で考えます。

仮に以下のルールを設定した場合、両者の関係は「1対多」となります。

ルール:会社は複数の社員を雇えるが、社員は1つの会社にしか属してはならない。

つまり、画像のような構造です。

「1(会社)対多(社員)」という構造ですね。

DB構造

この関係の場合は以下のようなDB構造にするのが一般的です。

●companies

id(int)

name(varchar)

●workers

id(int)

company_idint

name(varchar)

workersテーブルのcompany_idを使って、リレーション関係を構築します。

中間テーブルは必要ありません。

多対多リレーションシップ:中間テーブルが必要

先ほどと同様に「会社と社員」という関係性で考えた場合、以下のルールを設定した場合には「多対多」の関係となります。

ルール:会社は複数の社員を雇える、社員は複数の会社に所属して良い

つまり、画像のような構造です。

Cさん、Dさん、EさんがA社、B社両方に所属しています。

つまり「多(会社)対多(社員)」という構造になっています。

DB構造

●companies

id(int)

name(varchar)

●workers

id(int)

name(varchar)

※company_idはここには不要

workers_companies(中間テーブル)

id(int)

worker_id(int)

company_id(int)

companiesとworkersの関係を示すためのテーブルを別途作成し、ここに両方のIDを登録します。

このようなテーブルのことを中間テーブルといいます。

CakePHP4での使い方

以降は「CakePHPの基本がわかっている」という前提の内容です。

CakePHPの基本を知りたい方は以下をご参照ください。

DBを作成する

まずは以下の3つのテーブルをDB設定画面で作成してください。
※先述のDB構造と同内容です。

●companies

●workers

workers_companies(中間テーブル)

モデルを生成する

bakeコマンドでモデルを生成

以下のコマンドでモデルを作成します。

cake bake model companies
cake bake model workers
cake bake model workersCompanies

CakePHPでは、テーブル名をもとにリレーションを判断してくれるため、これだけで中間テーブルを用いた多対多連携を作成してくれます。

生成されるModelの内容

CompaniesTable.php
class CompaniesTable extends Table
{
 ・・・省略
    public function initialize(array $config): void
    {
        parent::initialize($config);

        $this->setTable('companies');
        $this->setDisplayField('name');
        $this->setPrimaryKey('id');

        $this->belongsToMany('Workers', [   //workersと多対多連携する
            'foreignKey' => 'company_id', //company_idを使ってcompaniesと連携する
            'targetForeignKey' => 'worker_id', //worker_idを使ってworkersと連携する
            'joinTable' => 'workers_companies',  //中間テーブルはworkers_companiesである
        ]);
    }
・・・省略

ポイントは以下の4点です。

①workersと多対多連携する(12行目)

②company_idを使ってcompaniesと連携する(13行目)

③worker_idを使ってworkersと連携する(14行目)

④中間テーブルはworkers_companiesである(15行目)

WorkersTable.php
class WorkersTable extends Table
{
・・・省略
    public function initialize(array $config): void
    {
        parent::initialize($config);

        $this->setTable('workers');
        $this->setDisplayField('name');
        $this->setPrimaryKey('id');

        $this->belongsToMany('Companies', [
            'foreignKey' => 'worker_id',
            'targetForeignKey' => 'company_id',
            'joinTable' => 'workers_companies',
        ]);
    }
・・・省略

CompaniesTableと対の関係になっていることがわかります。

WorkersCompaniesTable
class WorkersCompaniesTable extends Table
{
 ・・・省略
    public function initialize(array $config): void
    {
        parent::initialize($config);

        $this->setTable('workers_companies');
        $this->setDisplayField('id');
        $this->setPrimaryKey('id');

       //workersとcompaniesに両属する
        $this->belongsTo('Workers', [
            'foreignKey' => 'worker_id',
            'joinType' => 'INNER',
        ]);
        $this->belongsTo('Companies', [
            'foreignKey' => 'company_id',
            'joinType' => 'INNER',
        ]);
    }
・・・省略

12~20行目を見ると、中間テーブルはWorkersとCompaniesに両属していることがわかります。

Controllerからデータ取得

Controllerからデータを取得する際には、普通にcontainを使ってcompaniesに属するworkersや、逆にworkersに属するcompaniesを取得できます。

SamplesController.php

<?php
declare(strict_types=1);

namespace App\Controller;
use Cake\ORM\TableRegistry;

・・・省略
class SamplesController extends AppController
{
    /**
     * Index method
     *
     * @return \Cake\Http\Response|null|void Renders view
     */
    public function index()
    {
        $companiesTable = TableRegistry::getTableLocator()->get('Companies');
        $workersTable = TableRegistry::getTableLocator()->get('Workers');

        // id=1のcompanyを所属するworkerを含めて取得
        $company = $companiesTable->find()->contain(['Workers'])->->where(["id" => 1])->first();
        // id=1のworkerを所属するcompanyを含めて取得
        $worker = $workersTable->find()->contain(['Companies'])->->where(["id" => 1])->first();

        $this->set(compact('company','worker'));
    }

参考

CakePHP4公式:アソシエーション – モデル同士を繋ぐ

コメント

タイトルとURLをコピーしました