中間テーブルの利用シーン
中間テーブルを利用するのは、「多対多リレーションシップ」を実装する場合です。
類似の概念として「1対多リレーションシップ」があります。
まずは、両者の違いを解説します。
1対多リレーションシップ:中間テーブル不要
1対多の関係として、以下の事例を挙げます。
「会社(Company)」と「社員(Worker)」の関係で考えます。
仮に以下のルールを設定した場合、両者の関係は「1対多」となります。
ルール:会社は複数の社員を雇えるが、社員は1つの会社にしか属してはならない。
つまり、画像のような構造です。
「1(会社)対多(社員)」という構造ですね。
DB構造
この関係の場合は以下のようなDB構造にするのが一般的です。
●companies
id(int)
name(varchar)
●workers
id(int)
company_id(int)
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公式:アソシエーション – モデル同士を繋ぐ
コメント