こんにちは!フリーエンジニアのせきです。
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', ]);
まとめ
今回はテーブルを結合して検索する方法について解説しました。
実際のシステムでは複数のテーブルでデータを管理し、そのデータをユーザが扱いやすいように結合して表示したりするので、テーブルの結合は必須です。
テーブルを結合する方法を忘れてしまったら、この記事を思い出して下さい!