由於 CakePHP 的模組本身就是 ORM 的一種,所以在操作上有著迅速,便利,低風險(例如 SQL Injection)爾等好處。當然也不是沒有缺點,大概就是要犧牲掉一點效能吧。原生的 SQL 語法當然可以最佳化方式很多,當然衍生的問題也多。
首先,這裡先理解 Model 的運作方式,先是在 models
資料夾中建立一個模組,我們叫他 my_test
好了。他的內容基本上是這樣:
<?php
Class MyTest extends AppModel
{
var $name = "MyTest";
}
當我們這樣設定的時候,我們的資料庫也必須要有相對應的資料表(前輟 app_
是我自己的設定)。
然後我們就可以直接在 Controller 中使用。
<?php
Class HomepageController extends AppController
{
var $name = "Homapage";
var $uses = array("MyTest");
function index()
{
$result = $this->MyTest->find(...);
}
}
接下來要提到的是比較複雜的資料表關聯。在 CakePHP 中有幾種資料表之間的關聯模式,其實大家都一樣,就都是這幾種。
- 一對一(hasOne)
- 一對多(hasMany)
- 多對一(belongsTo)
- 多對多(hasAndBelongsToMany)
而不管是多少對多少,資料表之間一定得有個外鍵(foreignKey)來相互關聯。在 CakePHP 中預設是使用該資料加上底線 ID(_id
)的方式來作為預設的外鍵。所以如果我上述的例子有這種情況就是:
<?php
Class MyTest extends AppModel
{
var $name = "MyTest";
/**
* 我要跟 YourTest 關聯,那麼在 YourTest 就必須有一個外鍵欄位,預設是 my_test_id。
*/
var $hasOne = array(
'YourTest' => array (
'className' => 'YourTest'
)
);
}
SELECT
`MyTest`.`id`, `MyTest`.`label`, `MyTest`.`value`, `YourTest`.`id`, `YourTest`.`my_test_id`, `YourTest`.`value` FROM `app_my_tests`
AS `MyTest`
LEFT JOIN `app_your_tests` AS `YourTest`
ON (`YourTest`.`my_test_id` = `MyTest`.`id`)
WHERE 1 = 1;
而在 CakePHP 中的 belongsTo
則剛好跟 hasOne
相反,如果我的 YourTest 模組中設定了 belongsTo
給 MyTest,則所產生出來的查詢語法會跟上面的語法相似(幾乎是一樣的,只是主從關係對調)。
SELECT
`MyTest`.`id`, `MyTest`.`label`, `MyTest`.`value`, `YourTest`.`id`, `YourTest`.`my_test_id`, `YourTest`.`value` FROM `app_your_tests`
AS `YourTest`
LEFT JOIN `app_my_tests` AS `MyTest`
ON (`MyTest`.`id` = `YourTest`.`my_test_id`)
WHERE 1 = 1;
那麼,所謂的 hasMany 跟 hasAndBelongsToMany
的應用呢?我們先列出一個簡單的例子:
<?php
Class MyTest extends AppModel
{
var $name = "MyTest";
/**
* 我要跟 YourTest 關聯,那麼在 YourTest 就必須有一個外鍵欄位,預設是 my_test_id。
*/
var $hasOne = array(
'YourTest' => array (
'className' => 'YourTest'
)
);
/**
* 我要跟 OtherTest 關聯,那麼在 OtherTest 就必須有一個外鍵欄位,預設是 my_test_id。
*/
var $hasMany = array(
'OtherTest' => array (
'className' => 'OtherTest'
)
);}
那我要怎麼使用?
<?php
Class HomepageController extends AppController
{
var $name = "Homapage";
var $uses = array("MyTest");
function index()
{
$result = $this->MyTest->find(...);
/**
* 一對多,我就可以這樣去查詢。他會自動幫你把外鍵關聯做好。
*/
$result = $this->MyTest->OtherTest->find(...);
}
}
請看:
<?php
Class OtherTest extends AppModel
{
var $name = "OtherTest";
/**
* 我要跟 MyTest 關聯。
*/
var $belongsTo = array(
'MyTest' => array (
'className' => 'MyTest'
)
);
<?php
Class HomepageController extends AppController
{
var $name = "Homapage";
var $uses = array("MyTest", "OtherTest");
function index()
{
$result = $this->MyTest->find(...);
/**
* 倘若 OtherTest 有設定 belongsTo MyTest 的話,可以直接這樣查詢。
* 請留意上面的 $uses 有多了一組 OtherTest 的設定。
*/
$result = $this->OtherTest->find(...); }
}
SELECT
`OtherTest`.`id`, `OtherTest`.`my_test_id`, `OtherTest`.`value`, `MyTest`.`id`, `MyTest`.`label`, `MyTest`.`value`
FROM `app_other_tests`
AS `OtherTest`
LEFT JOIN `app_my_tests` AS `MyTest`
ON (`OtherTest`.`my_test_id` = `MyTest`.`id`)
WHERE 1 = 1;
<?php
Class MyTest extends AppModel
{
var $name = "MyTest";
/**
* 這裡做多對多查詢,子查詢是 YourTest,然後關聯 OtherTest 做條件比對。
*/
var $hasAndBelongsToMany = array(
'YourTest' => array (
'className' => 'YourTest',
'joinTable' => 'other_tests',
'foreignKey' => 'my_test_id',
'associationForeignKey' => 'your_test_id'
// 後面參數就先略過了。
)
);}
首先,他會先去 MyTest 去找資料,找到之後,再去 YourTest 與 OtherTest 做關聯查詢。我把關聯查詢的部份 Query 語法貼出來給大家參考一下:
# 其中 bf076000-81de-11e0-93d9-d8d3852f9be4 是 MyTest 查詢出來的 my_test_id 值。
SELECT
`YourTest`.`id`, `YourTest`.`your_test_id`, `YourTest`.`my_test_id`, `YourTest`.`value`, `OtherTest`.`id`, `OtherTest`.`other_test_id`, `OtherTest`.`my_test_id`, `OtherTest`.`value`
FROM `app_your_tests` AS `YourTest`
JOIN `app_other_tests` AS `OtherTest`
ON (
`OtherTest`.`my_test_id` = 'bf076000-81de-11e0-93d9-d8d3852f9be4'
AND `OtherTest`.`other_test_id` = `YourTest`.`id`
)
再解釋一次多對多的設定方法:
<?php
var $hasAndBelongsToMany = array(
/**
* 我的次查詢將交給 YourTest 去做。
*/
'YourTest' => array (
'className' => 'YourTest',
/**
* 這個查詢將會加入 other_tests 這個表格(注意沒有前輟)。
*/
'joinTable' => 'other_tests',
/**
* 設定加入的表格(OtherTest)的外鍵對應欄位:my_test_id
* 加入的表格的外鍵欄位值,必須符合第一次查詢(MyTest)傳入值。
* MyTest 傳入預設是使用 MyTest 的主鍵值(MyTest.id)。
*/
'foreignKey' => 'my_test_id',
/**
* 設定加入的表格(OtherTest)與次查詢表格的外鍵對應欄位為:your_test_id
* 次查詢 YourTest 的主鍵值,需要符合加入的表格(OtherTest)的值。
*/
'associationForeignKey' => 'your_test_id'
// 後面參數就先略過了。
)
);}
順序大致上是這樣的:
- 我先查詢(MyTest)
- 將主鍵值丟給最後一個資料表(OtherTest)
- 最後一個資料表比對外鍵,並且自身與第二資料表外鍵關聯。
- 最後取出所有資料(最後一個表會直接建立在第二資料表的輸出陣列內)。
輸出的結果大致上會像是這樣:
Array
(
[MyTest] => Array
(
[id] => bf076000-81de-11e0-93d9-d8d3852f9be4
[your_test_id] => 1
[other_test_id] => 1
[label] => 1
[value] => 1
)
[YourTest] => Array
(
[0] => Array
(
[id] => c7a46992-81de-11e0-93d9-d8d3852f9be4
[your_test_id] => 1
[my_test_id] => bf076000-81de-11e0-93d9-d8d3852f9be4
[value] => 1
[OtherTest] => Array
(
[id] => b6041304-81de-11e0-93d9-d8d3852f9be4
[other_test_id] => 1
[my_test_id] => bf076000-81de-11e0-93d9-d8d3852f9be4
[your_test_id] => c7a46992-81de-11e0-93d9-d8d3852f9be4
[value] => 1
)
)
)
)
私心建議,資料結構簡單清楚明瞭,會比用複雜的查詢來得好得多。