こんにちは!フリーエンジニアのせきです。
CakePHPには、関連のあるテーブルを結合して検索する方法がいくつか用意されています。
この記事では、
・関連のあるテーブルをモデルに定義する方法を知りたい
・テーブルを結合して検索する方法を知りたい
という基本的な内容から、
・複数のテーブルと結合する方法を知りたい
といった応用的な内容に関しても解説していきます。
今回はそんなCakePHPでテーブルを結合して検索する方法について、わかりやすく解説します!
モデルを関連付けて検索する方法
サンプルでは、以下のような受注テーブルを作成して、一覧表示します。
テーブル定義
CREATE TABLE ACCEPT_ORDER
(
ID INT NOT NULL AUTO_INCREMENT, -- 受注ID
CUSTOMER_ID INT, -- 顧客ID
PRODUCT_ID INT, -- 製品ID
QUANTITY INT, -- 数量
PRIMARY KEY (ID)
);
データ
| ID | CUSTOMER_ID | PRODUCT_ID | QUANTITY |
|---|---|---|---|
| 1 | 1 | 2 | 2 |
| 2 | 2 | 2 | 1 |
| 3 | 2 | 3 | 1 |
| 4 | 3 | 1 | 3 |
| 5 | 3 | 3 | 2 |
一覧に顧客名を表示できるよう、顧客テーブルを作成します。
テーブル定義
CREATE TABLE CUSTOMER
(
ID INT NOT NULL AUTO_INCREMENT, -- 顧客ID
CUSTOMER_NAME VARCHAR(32), -- 顧客名
PRIMARY KEY (ID)
);
データ
| ID | CUSTOMER_NAME |
|---|---|
| 1 | Tanaka |
| 2 | Saito |
| 3 | Yamada |
受注テーブル(ACCEPT_ORDER)のCUSTOMER_IDと顧客テーブル(CUSTOMER)のIDが紐づくものとし、受注テーブルに顧客テーブルを結合して検索します。
受注テーブルと顧客テーブルは、1つの顧客IDに対し複数の受注データが存在するので、「多 対 1」という関係になります。
CakePHPのモデルでは、「多 対 1」の関連をbelongsToで定義します。
belongsTo(結合するモデル名);
受注テーブルのModelは以下のようになります。
[プロジェクトのパス]/src/Model/Table/AcceptOrderTable.php
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class AcceptOrderTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Customer');
}
}
「$this->belongsTo(‘Customer’);」で、顧客テーブルとの関連を定義しています。
テーブルの項目名を「結合するテーブル名_ID」にしておくと、自動でその項目をキーに結合します。
結合される顧客テーブルのModelには、関連の定義は不要です。
[プロジェクトのパス]/src/Model/Table/CustomerTable.php
<?php
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class CustomerTable extends Table
{
}
Entityはそのままです。
[プロジェクトのパス]/src/Model/Entity/AcceptOrder.php
<?php
namespace App\Model\Entity;
use Cake\ORM\Entity;
class AcceptOrder extends Entity
{
}
[プロジェクトのパス]/src/Model/Entity/Customer.php
<?php
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Customer extends Entity
{
}
Controllerで検索します。
関連のあるテーブルを結合して検索するには、containを使用し以下のように記述します。
find('all')->contain(結合するモデル名);
受注テーブルの一覧を表示するControllerです。
[プロジェクトのパス]/src/Controller/AcceptOrderController.php
<?php
namespace App\Controller;
use App\Controller\AppController;
class AcceptOrderController extends AppController
{
public function index()
{
$query = $this->AcceptOrder->find('all')->contain(['Customer']);
$this->set('acceptOrder', $query);
}
}
「$this->AcceptOrder->find(‘all’)->contain([‘Customer’])」で、顧客テーブルと結合しています。
結合したテーブルの値は、以下のように取得できます。
$変数名->結合したテーブル名
結合したテーブル名は、すべて小文字で記述します。
受注テーブルの一覧を表示するTemplateです。
[プロジェクトのパス]/src/Template/AcceptOrder/index.ctp
<table cellpadding="0" cellspacing="0">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">CUSTOMER_NAME</th>
<th scope="col">PRODUCT_ID</th>
<th scope="col">QUANTITY</th>
</tr>
</thead>
<tbody>
<?php foreach ($acceptOrder as $acceptOrder): ?>
<tr>
<td><?= $acceptOrder->ID ?></td>
<td><?= $acceptOrder->customer->CUSTOMER_NAME ?></td>
<td><?= $acceptOrder->PRODUCT_ID ?></td>
<td><?= $acceptOrder->QUANTITY ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
「$acceptOrder->customer->CUSTOMER_NAME」で、結合した顧客テーブルの顧客名を表示しています。
「http://[サーバ名]/[プロジェクト名]/acceptOrder」にアクセスすると、以下のように表示されます。

検索時にJOINを追加する方法
Modelには手を加えず、Controllerで検索する時に結合する方法もあります。
検索を行うfind()は、「->」(アロー演算子)を続けて、様々なオプションや条件を指定することができます。
ここではテーブルの結合を行うjoin()とleftJoin()を解説します。
join()を使用する方法
join()には、配列で以下のような指定をします。
join([
'table' => 結合するテーブル名,
'alias' => テーブル別名,
'type' => 結合方法,
'conditions' => 結合する条件
])
結合方法には「LEFT」「RIGHT」「INNER」が指定できます。
conditionsに指定した条件で、tableに指定したテーブルと結合します。
さらに、select()を使用して、取得する項目を指定します。
select([
別名 => モデル名.項目名,
・・・
])
join()を使用したControllerです。
[プロジェクトのパス]/src/Controller/AcceptOrderController.php
<?php
namespace App\Controller;
use App\Controller\AppController;
class AcceptOrderController extends AppController
{
public function index()
{
$query = $this->AcceptOrder->find()
->join([
'table' => 'customer',
'alias' => 'c',
'type' => 'LEFT',
'conditions' => 'c.id = AcceptOrder.customer_id',
])->select([
'id' => 'AcceptOrder.id',
'customer_name' => 'c.customer_name',
'product_id' => 'AcceptOrder.product_id',
'quantity' => 'AcceptOrder.quantity',
]);
$this->set('acceptOrder', $query);
}
}
テーブルの値は、以下のように取得できます。
$変数名[selectで指定した別名]
これを使用して、Templateは以下のようになります。
[プロジェクトのパス]/src/Template/AcceptOrder/index.ctp
<table cellpadding="0" cellspacing="0">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">CUSTOMER_NAME</th>
<th scope="col">PRODUCT_ID</th>
<th scope="col">QUANTITY</th>
</tr>
</thead>
<tbody>
<?php foreach ($acceptOrder as $acceptOrder): ?>
<tr>
<td><?= $acceptOrder['id'] ?></td>
<td><?= $acceptOrder['customer_name'] ?></td>
<td><?= $acceptOrder['product_id'] ?></td>
<td><?= $acceptOrder['quantity'] ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
「http://[サーバ名]/[プロジェクト名]/acceptOrder」にアクセスすると、モデルを関連付けて検索した時と同じ画面が表示されます。
leftJoin()を使用する方法
join()で結合方法に「LEFT」を指定する場合には、leftJoin()を使うこともできます。
leftJoin()には、以下のような指定をします。
leftJoin(
[テーブル別名 => 結合するテーブル名],
[結合する条件]
)
leftJoin()を使用したControllerです。
[プロジェクトのパス]/src/Controller/AcceptOrderController.php
<?php
namespace App\Controller;
use App\Controller\AppController;
class AcceptOrderController extends AppController
{
public function index()
{
$query = $this->AcceptOrder->find()
->leftJoin(
['c' => 'customer'],
['c.id = AcceptOrder.customer_id']
)->select([
'id' => 'AcceptOrder.id',
'customer_name' => 'c.customer_name',
'product_id' => 'AcceptOrder.product_id',
'quantity' => 'AcceptOrder.quantity',
]);
$this->set('acceptOrder', $query);
}
}
Templateはjoin()の時と同じもので、同じ画面が表示されます。
複数のテーブルと結合する方法
1つのテーブルに、複数のテーブルを結合することもできます。
製品テーブルを作成し、一覧に製品名も表示するようにします。
テーブル定義
CREATE TABLE PRODUCT
(
ID INT NOT NULL AUTO_INCREMENT, -- 製品ID
PRODUCT_NAME VARCHAR(32), -- 製品名
UNIT_PRICE INT, -- 値段
PRIMARY KEY (ID)
);
データ
| ID | PRODUCT_NAME | UNIT_PRICE |
|---|---|---|
| 1 | Bag | 5000 |
| 2 | Shoes | 8000 |
| 3 | Hat | 3000 |
Modelに関連を複数定義する場合は、belongsToを追加します。
[プロジェクトのパス]/src/Model/Table/AcceptOrderTable.php
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class AcceptOrderTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Product'); // 追加
$this->belongsTo('Customer');
}
}
Controllerのcontainにも追加します。
[プロジェクトのパス]/src/Controller/AcceptOrderController.php
<?php
namespace App\Controller;
use App\Controller\AppController;
class AcceptOrderController extends AppController
{
public function index()
{
$query = $this->AcceptOrder->find('all')->contain(['Product', 'Customer']); // 'Product'追加
$this->set('acceptOrder', $query);
}
}
製品IDではなく製品名を表示するよう、Templateを修正します。
[プロジェクトのパス]/src/Template/AcceptOrder/index.ctp
<table cellpadding="0" cellspacing="0">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">CUSTOMER_NAME</th>
<th scope="col">PRODUCT_NAME</th>
<th scope="col">QUANTITY</th>
</tr>
</thead>
<tbody>
<?php foreach ($acceptOrder as $acceptOrder): ?>
<tr>
<td><?= $acceptOrder->ID ?></td>
<td><?= $acceptOrder->customer->CUSTOMER_NAME ?></td>
<td><?= $acceptOrder->product->PRODUCT_NAME ?></td>
<td><?= $acceptOrder->QUANTITY ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
「$acceptOrder->product->PRODUCT_NAME」で製品テーブルの製品名が取得できます。
「http://[サーバ名]/[プロジェクト名]/acceptOrder」にアクセスすると、製品名が表示されています。

Contorollerのjoin()でも、複数のテーブルを結合することができます。
以下のように、join()にテーブル別名をキーにした連想配列を指定します。
$query = $this->AcceptOrder->find()
->join([
'p' => [
'table' => 'product',
'type' => 'LEFT',
'conditions' => 'p.id = AcceptOrder.product_id',
],
'c' => [
'table' => 'customer',
'type' => 'LEFT',
'conditions' => 'c.id = AcceptOrder.customer_id',
]
])->select([
'id' => 'AcceptOrder.id',
'customer_name' => 'c.customer_name',
'product_name' => 'p.product_name',
'quantity' => 'AcceptOrder.quantity',
]);
まとめ
今回はテーブルを結合して検索する方法について解説しました。
実際のシステムでは複数のテーブルでデータを管理し、そのデータをユーザが扱いやすいように結合して表示したりするので、テーブルの結合は必須です。
テーブルを結合する方法を忘れてしまったら、この記事を思い出して下さい!






