第三節
Model的變數
在建立model時,我們可以設一些特殊的變數,啟用Cake的某些功能:
$primaryKey
如果這個model的對應資料表,主鍵不叫'id',就必須使用這個變數告訴Cake主鍵的名稱。
$recursive
這個值可以指定Cake在執行find()和findAll()時向內尋找的層數。
想像一下有很多小組(Groups),每個小組內有很多位成員(Users),每位成員擁有很多文章(Articles),則不同的設定會有以下不同的結果:
| $recursive = 0 | Cake只撈出小組資料就結束了 |
| $recursive = 1 | Cake撈出小組與小組內的成員資料 |
| $recursive = 2 | Cake 不但撈出小組、組內的成員,也找到成員所發表的文章 |
$transactional
告訴Cake是否啟用交易(就是begin/commit/rollback)。設定值為true或false。此變數只有在資料庫支援交易時才有用。
$useTable
如果model使用的資料表名稱不是model名稱的複數型(加s),而你又不想改變資料表的名稱,這個變數能設定model使用的資料表。
$validate
用來檢驗傳入model的資料的陣列,請參考"資料檢驗"一章。
$useDbConfig
記得一開始我們在/app/config/database.php設定資料庫連接的資料嗎?用這個變數可以選擇要使用那一組設定-
先在資料庫設定檔內做好幾組不同的設定,再把這個變數設定成欲使用設定的名稱即可。預設會使用的正是你所知的'default'。
第四節
關聯
引言
CakePHP具有一項強而有力的特色,就是model所提供的相關性對應。
CakePHP中資料表透過關聯來連結。
關聯是邏輯相關的各單元間的黏著劑。
CakePHP含有四種關聯:
hasOne
hasMany
belongsTo
hasAndBelongsToMany
當model間定義了關聯設定,Cake會自動由資料表內撈出資料。
舉個例來說,如果有個Post model使用hasMany關聯與Author model連結。
當呼叫controller中的$this->Post->findAll()時,會同時撈出Post與相關的Author資料。
如果想正確的使用關聯,最好能尊守CakePHP的命名規則(參考附錄"命名規則")。
正確地使用CakePHP命名規則,即可啟用Scafold機制將資料以視覺元件顯示出來,因為Scafold機制會偵測並使用model間的關聯。
當然你也可以不透過Cake的命名規則,以自己的方法建立model間的關聯,方法我們稍後再介紹,目前先以命名規則的方法來建立關聯。
現在要介紹資料表名稱、model名稱和外部鍵的命名規則。
下面列出來的是Cake期待這幾個元素應該有的名稱:
資料表名稱: [代表物件的複數]。例如我們想存blog的文章(post)和文章的作者(author),資料表名稱就定為posts和authors。
Model名稱: [資料表名稱的單數]。
"posts"資料表對應的model名稱就是"Post",而"authors"資料表對應的model名稱就叫"Author"。
外部鍵: [model名稱的單數]_id。
例如,"authors"資料表內含一個外部鍵指向"posts"資料表,這個外部鍵就叫"post_id".
CakePHP的Scafold機制把欄位順序的視為關聯的順序。
例如,有個Article資料表關聯於Author,Editor和Publisher三個model ,
需要三個外部鍵:author_id,,editor_id與publisher_id。
Scafold機制就會自動以三個欄位在資料表中順序建立關聯,第一是Author,第二是Editor,最後是Publisher。
為了要顯示這些關聯的運作情形,我們繼續使用blog程式當範例。
整個情境是這樣的,現在我們想要建立一個簡單的使用者管理系統,這麼做當然是為了持續追蹤使用者。
我們希望讓每個使用者有一組資料設定(User hasOne Profile),同時也可以發表很多自己的意見(User hasMany Comments)。
使用者系統建立後,我們又想讓文章擁有很多標籤(Post hasAndBelongsToMany Tags)。
定義與使用hasOne查詢
在操作接下來的動作前,我假設你已經事先建立好User和Profile model了。
要在他們之間建立hasOne關聯,必須在model中加入一個陣列告訴Cake他們的關聯性,看起來像這樣:
/app/models/user.php hasOne
<?php
class User extends AppModel
{
var $name = 'User';
var $hasOne = array('Profile' =>
array('className' => 'Profile',
'conditions' => '',
'order'
=> '',
'dependent' =>
true,
'foreignKey' =>
'user_id'
)
);
}
?>hasOne陣列正是Cake用來建立User和Profile二個model之間關聯的關鍵。陣列中的每個設定可以讓您更仔細地設計關聯性:
className (必須):相要建立關聯的model名稱。
以我們的例子來說,就設定成'Profile'。
conditions:用SQL語法陳述此關係成立的額外限制
我們可以用這個設定告訴Cake只有在Profile的標題顏色為綠色時才建立關聯。
此時,只要把conditions的值設成像這樣:"Profile.header_color = 'green'"。
order: 關聯的model排序的規則
如果想讓model照某個規則排序,可以用SQL語法寫好,設定在此:例如"Profile.name
ASC"。
dependent:如果設定成true,則當model資料被刪除時,相關的model也會一併被刪。
例如,如果"Cool Blue"關聯於"Bob",當我刪除"Bob"時,"Cool Blue"也會一併被刪除。
foreignKey: 指向關聯model的外部鍵名稱。
如果沒有依照Cake的命名規則建立資料表,就必須在此設定外部鍵的名稱。
現在執行User的find()或findAll()函式,可以看到Profile model也跟著被撈出相關的資料。
$user = $this->User->read(null, '25');
print_r($user);
//輸出:
Array
(
[User] => Array
(
[id] => 25
[first_name] => John
[last_name] => Anderson
[username] => psychic
[password] => c4k3roxx
)
[Profile] => Array
(
[id] => 4
[name] => Cool Blue
[header_color] => aquamarine
[user_id] = 25
)
)定義與使用belongsTo查詢
現在User可以看到它的Profile了。
接著再建立另一種關聯,讓Profile也能看到User。
這就是belongsTo在Cake中所發揮的功能。
我們接著在Profile model裡做這件事:
/app/models/profile.php belongsTo
<?php
class Profile extends AppModel
{
var $name = 'Profile';
var $belongsTo = array('User' =>
array('className' => 'User',
'conditions' => '',
'order'
=> '',
'foreignKey' =>
'user_id'
)
);
}
?>$belongsTo陣列就是Cake用來建立User和Profile model間關聯的關鍵。
裡頭的每個選項可以用來更仔細的設計此關聯。
className (必須):想要建立關聯的model名稱
以我們的例子為例,這個值就是'User'。
conditions:用SQL語法陳述此關係成立的額外限制
用這個設定值告訴Cake只有在User啟用時,此關聯才成立。
此時這個值要設定成"User.active = '1'"或類似的樣子。
order: model的排序規則
如果想要讓model依特定的方式排序,就用SQL命定陳述規則,放在這裡,例如:"User.last_name ASC"。
foreignKey:指向關聯model的外部鍵
若沒有依Cake的命名規則建立資料表,就必須在此設定外部鍵名稱。
現在我們再執行一次Profile的file()或findAll(),就可以很明顯的看到User model也一起被查詢出相關資料:
$profile = $this->Profile->read(null, '4');
print_r($profile);
//output:
Array
(
[Profile] => Array
(
[id] => 4
[name] => Cool Blue
[header_color] => aquamarine
[user_id] = 25
)
[User] => Array
(
[id] => 25
[first_name] => John
[last_name] => Anderson
[username] => psychic
[password] => c4k3roxx
)
)定義與使用hasMany查詢
現在,已經將User和Profile二個model建立起關聯,且運作正常,接下來試著為User和Comment間建立關聯。
結果,User model看起來會像這樣:
/app/models/user.php hasMany
<?php
class User extends AppModel
{
var $name = 'User';
var $hasMany = array('Comment' =>
array('className' =>
'Comment',
'conditions'
=> 'Comment.moderated = 1',
'order'
=> 'Comment.created DESC',
'limit'
=> '5',
'foreignKey'
=> 'user_id',
'dependent'
=> true,
'exclusive'
=> false,
'finderQuery'
=> ''
)
);
// 這是我們先前建立的另一個關聯
var $hasOne = array('Profile' =>
array('className' => 'Profile',
'conditions' => '',
'order'
=> '',
'dependent' =>
true,
'foreignKey' =>
'user_id'
)
);
}
?>$hasMany陣列就是Cake建立User和Comment間關聯的關鍵。陣列內的每個項目可以
讓你更仔細的設計關聯:
className (必須): 想要建立關聯的model名稱
以我們的例子為例,這個值就是'Comment'。
conditions: 用SQL語法陳述此關係成立的額外限制
若想告訴Cake只找出"溫合"的意見,只要把這個值設成"Comment.moderated = 1"或類似的值。
order: model的排序規則
如果想讓關聯資料表以特定的方式排序,可以將SQL指令敍述放在這裡,例如:"Comment.created DESC"。
limit:Cake從關聯資料表撈出的資料數上限
例如,我們不想看所有人的見意,只想看5條。
foreignKey: 指向關聯model的外部鍵
若沒有依Cake的命名規則建立資料表,就必須在此設定外部鍵名稱。
dependent: 如果設成true,則在這個資料被刪時,關聯的資料也會一併被刪。
例如,假設"Cool Blue"關聯於"Bob",當我把"Bob"刪除時,"Cool Blue"也會一起被刪掉。
exclusive: 如果設成true,則關聯物件被刪除前,不會呼叫beforeDelete回呼涵數。
對簡單的關聯非常有用,可以加快執行速度。
finderQuery: 用SQL指令建立關聯
這兒可以對多個資料表進行複雜的關聯。如果Cake自動做的關聯不合你用,就在這兒自己寫一個吧。
現在執行User model的find()或findAll()看看,應該會發現相關聯的Comment model也一併被撈出資料了:
$user = $this->User->read(null, '25');
print_r($user);
//output:
Array
(
[User] => Array
(
[id] => 25
[first_name] => John
[last_name] => Anderson
[username] => psychic
[password] => c4k3roxx
)
[Profile] => Array
(
[id] => 4
[name] => Cool Blue
[header_color] => aquamarine
[user_id] = 25
)
[Comment] => Array
(
[0] => Array
(
[id] => 247
[user_id] => 25
[body] => The hasMany assocation is nice to have.
)
[1] => Array
(
[id] => 256
[user_id] => 25
[body] => The hasMany assocation is really nice to have.
)
[2] => Array
(
[id] => 269
[user_id] => 25
[body] => The hasMany assocation is really, really nice to have.
)
[3] => Array
(
[id] => 285
[user_id] => 25
[body] => The hasMany assocation is extremely nice to have.
)
[4] => Array
(
[id] => 286
[user_id] => 25
[body] => The hasMany assocation is super nice to have.
)
)
)雖然這理並沒有提及詳細的步驟,但最好同時定義"Comment bedongsTo User"的關聯,讓二個model可以互相看到。
當使用scaffold時就常常會沒有定義雙向的關聯。
定義與使用hasAndBelongsToMany
現在應該已經對這簡單的關聯機制很熟析了。
讓我們看看最後一種關聯:hasAndBelongsToMan(或簡稱HABTM)。
最後一種是最難理解的,但它也是最常用的的關聯。
當有二個資料表靠一個Join資料表連結在一起時,HABTM關聯立即發揮它的功用。
Join資料表通常只有二欄,分別關聯到二個資料表。
hasMany和hasAndBelongsToMany的相異處在於hasMany的關連性無法共享。
例如,一個使用者(User)有很多(hasMany)建議(comment),這樣的程式只能讓Comment資料與單一筆User資料產生關聯,
若用HABTM,Comment的資料可以和多筆User資料產生關聯。
馬上舉個例子看看:建立Post和Tag二個model的關聯。
如果一個Tag只能讓一個Post使用,Tag馬上就會用完,但我們不想這様,我們想要讓一個Tag可以讓多個Post使用。
為達此需求,我們必須先設計好正確的資料表。
所以,我們需要為Tag model建立"tags"資料表,為Post model建立"posts"資料表。
接下來要為他們建立一個join資料表,這是一個新的資料表,
Cake對這種資料表的命名規則為[第一組model名稱的複數]_[第二組model名稱的複數],model名稱要依字母排放:
HABTM Join資料表: 範例model和他們的join資料表名稱
Posts 和Tags: posts_tags
Monkeys 和 IceCubes: ice_cubes_monkeys
Categories 和 Articles: articles_categories
HABTM Join資料表最少需為連結的model建立二個外部鍵。以我們的例子來說就是"post_id"和"tag_id"。
要建立這三個資料表的SQL指令如下:
--
-- posts資料表的結構
--
CREATE TABLE `posts` (
`id` int(10) unsigned NOT NULL auto_increment,
`user_id` int(10) default NULL,
`title` varchar(50) default NULL,
`body` text,
`created` datetime default NULL,
`modified` datetime default NULL,
`status` tinyint(1) NOT NULL default '0',
PRIMARY KEY (`id`)
) TYPE=MyISAM;
-- --------------------------------------------------------
--
-- posts_tags資料表的結構
--
CREATE TABLE `posts_tags` (
`post_id` int(10) unsigned NOT NULL default '0',
`tag_id` int(10) unsigned NOT NULL default '0',
PRIMARY KEY (`post_id`,`tag_id`)
) TYPE=MyISAM;
-- --------------------------------------------------------
--
-- tags資料表的結構
--
CREATE TABLE `tags` (
`id` int(10) unsigned NOT NULL auto_increment,
`tag` varchar(100) default NULL,
PRIMARY KEY (`id`)
) TYPE=MyISAM;資料表建定好後就可以在Post model內定義關聯了:
/app/models/post.php hasAndBelongsToMany
<?php
class Post extends AppModel
{
var $name = 'Post';
var $hasAndBelongsToMany = array('Tag' =>
array('className'
=> 'Tag',
'joinTable' => 'posts_tags',
'foreignKey' => 'post_id',
'associationForeignKey'=> 'tag_id',
'conditions' => '',
'order' => '',
'limit' => '',
'unique' => true,
'finderQuery' => '',
'deleteQuery' => '',
)
);
}
?>$hasAndBelongsToMany陣列是Cake用來建立Post和Tag關聯的關鍵。每個項目可以用來更仔細的設定這個關聯的內容:
className (必要):想要建立關聯的model名稱
在我們這個例子中要設為'Tag'
joinTable:當資料庫沒照Cake命名規則建立時,就必須在此指定資料表名稱。
foreignKey:Join資料表內指向目前model的外部鍵名稱。
當資料庫沒照Cake命名規則建立時,就必須在此指定外部鍵名稱。
associationForeignKey:指向關聯資料表的外部鍵名稱。
conditions: 用SQL語法陳述此關係成立的額外限制
我們可以使用這個項目告訴Cake只找Tag資料表中有確認過的標簽籤。
這時要把這個值設定成"Tag.approved = 1"或類似的樣子。
order: model的排序規則
這個項目可以讓關聯model以特定的方式排列。得用SQL語法敍述,例如:"Tag.tag DESC"。
limit: 從關聯資料表中撈出的資料量上限
用來限制撈出的資料筆數上限。
unique: 如果設成true,則忽略重復的資料
基本上,如果關聯很明確,就可以把它設成true。
那麼"Awesomeness"就只能被指定到"Cake Model Associations"一次,只會在結果中出現一個。
finderQuery: 用完整的SQL指令描述關聯
這兒可以對多個資料表進行複雜的關聯。
如果Cake自動做的關聯不合你用,就在這兒自己寫一個吧。
deleteQuery: 用來刪除HABTM model間關聯資料的完整SQL指令
如果你不喜歡Cake的刪除行為,可以在此建立自己的方法來刪除。
當我們現在執行Post model裡的find()或findall(),應該看到Tag model同時也被撈出資料了:
$post = $this->Post->read(null, '2');
print_r($post);
//output:
Array
(
[Post] => Array
(
[id] => 2
[user_id] => 25
[title] => Cake Model Associations
[body] => Time saving, easy, and powerful.
[created] => 2006-04-15 09:33:24
[modified] => 2006-04-15 09:33:24
[status] => 1
)
[Tag] => Array
(
[0] => Array
(
[id] => 247
[tag] => CakePHP
)
[1] => Array
(
[id] => 256
[tag] => Powerful Software
)
)
)儲存相關聯的Model資料
使用關聯式model時有個重點要緊記在心:
所有相關聯的model內,只要其中一個進行儲存,其他的也會跟著儲存。
例如現在要儲存Post和關聯的Comment,想透過Post或Comment model儲存都可以。
如果相關聯的model內其中一個資料還沒建立(例如,我們想同時存入新的Post和它的註解),則必須先存主(或父)model。
舉個例子,相像一下在PostController裡有個動作是儲存新的Post和相關的Comment。
下面的動作會假設你已經發表一個單一的Post和單一個Comment。
/app/controllers/posts_controller.php (部分)
function add()
{
if (!empty($this->data))
{
//我們可以存Post資料:
//它應該會被放在$this->data['Post']
$this->Post->save($this->data);
//現在我們需要儲存Comment資料
//但我們必須先得知剛剛存入的Post的id
$post_id = $this->Post->getLastInsertId();
//然後把這個資料加入欲儲存的資料中
//並且儲存comment
$this->data['Comment']['post_id'] = $post_id;
//因為Post有很多(hasMany)Comments(他們有關聯),我們可以
//透過Post model存取Comment model:
$this->Post->Comment->save($this->data);
}
}然而,如果父model已經存於系統中(例如,現在要在已經存在的Post上再加一個Comment),
在儲存前需要先知道這個Post在父model內的ID值。
你可以透過URL參數(或當作表單中一個隱藏的元件)把ID傳過來。
/app/controllers/posts_controller.php (部分)
//如果用URL參數傳遞,則看起來會長這様
function addComment($post_id)
{
if (!empty($this->data))
{
//你可能會覺得這麼做對$post_id的安全性不高,
//其實這麼做只是為了做示範,夠用了..
$this->data['Comment']['post_id'] = $post_id;
//因為我們的Post和Comment間有hasMany的關聯,所以可以直
//接透過Post model存取Comment model:
$this->Post->Comment->save($this->data);
}
}如果透過表單中隱藏欄位傳遞資訊,那麼先幫欄位取個名字(如果是使用HtmlHelper),它就會出現在POST資料裡應該出現的地方:
如果post的ID是在$post['Post']['id']...
<?php echo $html->hidden('Comment/post_id', array('value' => $post['Post']['id'])); ?>透過這樣的方式,Post資料的ID自動被填到$this->data['Comment']['post_id']裡,
儲存時就只需呼叫$this->Post->Comment->save($this->data)。
同樣的技巧也可以用在儲存多個子model,只要把這些save()的呼叫放在迴圈中(記得使用Model::Create()清除model資訊)。
總而言之,如果要儲存相關聯的資料(對belongsTo,hasOne,和hasMany來說),就是要先取得資料在父model的ID值,再存到子model中。
儲存 hasAndBelongsToMany 的關聯資料
儲存具有hasOne、belongsTo和hasMany關聯的資料十分容易:
只要把外部鍵和ID值開放,結束時就於model裡呼叫save()即可,所有事都會自動連結好。
但如果是儲存具有hasAndBelongsToMany的資料,就有點技巧,但我們會用我們的方法讓它盡可能的簡單。
跟著我們的範例走前,必須先做一些資料表,建立Tags與Posts的關聯。
先建立一個用來新增post的表單,然後把他們關聯到一組已存在的Tag上。
或許你想建一個可以新增Tag然後動態進行關聯動作的表單。
但為了讓事情單純化,這裡只示範如果進行關聯動作,其他的留給你做。
在Cake中讓model自己儲存資料的話,tag的名字看起來會類似'Model/field_name'(如果你是使用Html Helper)。
我們直接從新增文章的表單那部分開始說:
/app/views/posts/add.thtml 新增文章的表單
<h1>Write a New Post</h1>
<table>
<tr>
<td>Title:</td>
<td><?php echo $html->input('Post/title')?></td>
</tr>
<tr>
<td>Body:<td>
<td><?php echo $html->textarea('Post/body')?></td>
</tr>
<tr>
<td colspan="2">
<?php echo
$html->hidden('Post/user_id',
array('value'=>$this->controller->Session->read('User.id')))?>
<?php echo $html->hidden('Post/status' , array('value'=>'0'))?>
<?php echo $html->submit('Save Post')?>
</td>
</tr>
</table>
這樣的表單只能新增一筆文章資料。再加上一些程式碼,可以在文章上加一個或多個標籤:
/app/views/posts/add.thtml (加入了標籤相關的程式碼)
<h1>Write a New Post</h1>
<table>
<tr>
<td>Title:</td>
<td><?php echo $html->input('Post/title')?></td>
</tr>
<tr>
<td>Body:</td>
<td><?php echo $html->textarea('Post/body')?></td>
</tr>
<tr>
<td>Related Tags:</td>
<td><?php echo
$html->selectTag('Tag/Tag', $tags, null, array('multiple' =>
'multiple')) ?>
</td>
</tr>
<tr>
<td colspan="2">
<?php echo
$html->hidden('Post/user_id',
array('value'=>$this->controller->Session->read('User.id')))?>
<?php echo $html->hidden('Post/status' , array('value'=>'0'))?>
<?php echo $html->submit('Save Post')?>
</td>
</tr>
</table>
為了在呼叫controller裡的$this->Post->save()後可以把新的文章和關聯的標籤存起來,
欄位名稱要叫作"Tag/Tag"(欄位屬性會看起來像'data[ModelName][ModelName][]')。
傳回的資料必須是單獨一個ID或一組ID的陣列。因為這裡我們使用多選元件,傳回的結果會是一個ID的陣列。
$tags變數是一個陣列,內部的鍵是所有可能的標籤ID,值會在多選選單元件上顯示標籤的名稱。
使用bindModel()和unbindModel()動態改變關聯
有時候我們會想在某些狀況時動態改變資料間的關聯性。
如果你覺得model檔裡定義了太多關聯的資訊,可以用bindModel和unbindModel為下一個查詢呼叫時建立或解除關聯性。
我們先做二個model來示簵bindModel()和unbindMode()的運作狀況:
leader.php 和 follower.php
<?php
class Leader extends AppModel
{
var $name = 'Leader';
var $hasMany = array(
'Follower' => array(
'className' => 'Follower',
'order' => 'Follower.rank'
)
);
}
?>
<?php
class Follower extends AppModel
{
var $name = 'Follower';
}
?>在LeadersController中可以用Leader Model的find()方法找到一個領袖和他的追隨者。
如上面所示,Leader Model裡定義了一組關聯陣列,說明了"領袖有很多追隨者"。
為了示範,讓我們用unbindModel()解除這個領袖的追隨者。
leaders_controller.php (部分)
function someAction()
{
//這裡會撈出領袖和追隨者的資料
$this->Leader->findAll();
//移除hasMany關聯...
$this->Leader->unbindModel(array('hasMany' => array('Follower')));
//現在再查詢一次,就只會傳回一個沒有追隨者的領袖
$this->Leader->findAll();
//註: unbindModel只會影響下一個查詢函式
//接下來的查詢函式會自動恢復原始設定
//我們已經在unbindModel()後呼叫過一次findAll(), 所以
//這次查詢結果會再度撈出領袖和他的追隨者...
$this->Leader->findAll();
}將unbindModel函式用在其他種類的關聯上的方法也都類似:
只要改變關聯名稱和model的名稱就行了。unbindModel()的基本用法如下:
通用的 unbindModel() 範例
$this->Model->unbindModel(array('associationType' => array('associatedModelClassName')));現在已經成功的動態解除關聯,接下來讓我們動態加上一個關聯。
這個看起來沒有什麼信條的領袖,需要加上一些信條。
Principle model的程式碼很簡單,裡頭什麼都沒有,只有一行指定$name的程式。
現在就動態為領袖加上一些信條(一樣只有下一個查詢有用):
leaders_controller.php (部分)
funciton anotherAction()
{
//在leader.php的model檔中沒有在Leader和Priciple間建立hasMan關聯,
//所以這個查詢動作只會撈出領袖資料
$this->Leader->findAll();
//用bindModel()在Leader和Priciple間建立hasMany的關聯
$this->Leader->bindModel(
array('hasMany' => array(
'Principle' => array(
'className' => 'Principle'
)
)
)
);
//現在已經建立好關聯,在下一個查詢動作中可以同時撈出領袖和
//他的信條
$this->Leader->findAll();
}bindModel()可以輕易的加入新的關聯,如果想要改變排列規則或其他參數也很有用。
現在你已了解,bindModel的基本用法就是把一般的關聯陣列包在另一個鍵是關聯名稱的陣列中:
通用的bindModel()範例
$this->Model->bindModel(
array('associationName' => array(
'associatedModelClassName' => array(
// normal association keys go here...
)
)
)
);請注意動態設定時,必須確保執行時資料是正確的。
第三節
Model的變數
在建立model時,我們可以設一些特殊的變數,啟用Cake的某些功能:
$primaryKey
如果這個model的對應資料表,主鍵不叫'id',就必須使用這個變數告訴Cake主鍵的名稱。
$recursive
這個值可以指定Cake在執行find()和findAll()時向內尋找的層數。
想像一下有很多小組(Groups),每個小組內有很多位成員(Users),每位成員擁有很多文章(Articles),則不同的設定會有以下不同的結果:
| $recursive = 0 | Cake只撈出小組資料就結束了 |
| $recursive = 1 | Cake撈出小組與小組內的成員資料 |
| $recursive = 2 | Cake 不但撈出小組、組內的成員,也找到成員所發表的文章 |
$transactional
告訴Cake是否啟用交易(就是begin/commit/rollback)。設定值為true或false。此變數只有在資料庫支援交易時才有用。
$useTable
如果model使用的資料表名稱不是model名稱的複數型(加s),而你又不想改變資料表的名稱,這個變數能設定model使用的資料表。
$validate
用來檢驗傳入model的資料的陣列,請參考"資料檢驗"一章。
$useDbConfig
記得一開始我們在/app/config/database.php設定資料庫連接的資料嗎?用這個變數可以選擇要使用那一組設定-
先在資料庫設定檔內做好幾組不同的設定,再把這個變數設定成欲使用設定的名稱即可。預設會使用的正是你所知的'default'。
第四節
關聯
引言
CakePHP具有一項強而有力的特色,就是model所提供的相關性對應。
CakePHP中資料表透過關聯來連結。
關聯是邏輯相關的各單元間的黏著劑。
CakePHP含有四種關聯:
hasOne
hasMany
belongsTo
hasAndBelongsToMany
當model間定義了關聯設定,Cake會自動由資料表內撈出資料。
舉個例來說,如果有個Post model使用hasMany關聯與Author model連結。
當呼叫controller中的$this->Post->findAll()時,會同時撈出Post與相關的Author資料。
如果想正確的使用關聯,最好能尊守CakePHP的命名規則(參考附錄"命名規則")。
正確地使用CakePHP命名規則,即可啟用Scafold機制將資料以視覺元件顯示出來,因為Scafold機制會偵測並使用model間的關聯。
當然你也可以不透過Cake的命名規則,以自己的方法建立model間的關聯,方法我們稍後再介紹,目前先以命名規則的方法來建立關聯。
現在要介紹資料表名稱、model名稱和外部鍵的命名規則。
下面列出來的是Cake期待這幾個元素應該有的名稱:
資料表名稱: [代表物件的複數]。例如我們想存blog的文章(post)和文章的作者(author),資料表名稱就定為posts和authors。
Model名稱: [資料表名稱的單數]。
"posts"資料表對應的model名稱就是"Post",而"authors"資料表對應的model名稱就叫"Author"。
外部鍵: [model名稱的單數]_id。
例如,"authors"資料表內含一個外部鍵指向"posts"資料表,這個外部鍵就叫"post_id".
CakePHP的Scafold機制把欄位順序的視為關聯的順序。
例如,有個Article資料表關聯於Author,Editor和Publisher三個model ,
需要三個外部鍵:author_id,,editor_id與publisher_id。
Scafold機制就會自動以三個欄位在資料表中順序建立關聯,第一是Author,第二是Editor,最後是Publisher。
為了要顯示這些關聯的運作情形,我們繼續使用blog程式當範例。
整個情境是這樣的,現在我們想要建立一個簡單的使用者管理系統,這麼做當然是為了持續追蹤使用者。
我們希望讓每個使用者有一組資料設定(User hasOne Profile),同時也可以發表很多自己的意見(User hasMany Comments)。
使用者系統建立後,我們又想讓文章擁有很多標籤(Post hasAndBelongsToMany Tags)。
定義與使用hasOne查詢
在操作接下來的動作前,我假設你已經事先建立好User和Profile model了。
要在他們之間建立hasOne關聯,必須在model中加入一個陣列告訴Cake他們的關聯性,看起來像這樣:
/app/models/user.php hasOne
<?php
class User extends AppModel
{
var $name = 'User';
var $hasOne = array('Profile' =>
array('className' => 'Profile',
'conditions' => '',
'order'
=> '',
'dependent' =>
true,
'foreignKey' =>
'user_id'
)
);
}
?>hasOne陣列正是Cake用來建立User和Profile二個model之間關聯的關鍵。陣列中的每個設定可以讓您更仔細地設計關聯性:
className (必須):相要建立關聯的model名稱。
以我們的例子來說,就設定成'Profile'。
conditions:用SQL語法陳述此關係成立的額外限制
我們可以用這個設定告訴Cake只有在Profile的標題顏色為綠色時才建立關聯。
此時,只要把conditions的值設成像這樣:"Profile.header_color = 'green'"。
order: 關聯的model排序的規則
如果想讓model照某個規則排序,可以用SQL語法寫好,設定在此:例如"Profile.name
ASC"。
dependent:如果設定成true,則當model資料被刪除時,相關的model也會一併被刪。
例如,如果"Cool Blue"關聯於"Bob",當我刪除"Bob"時,"Cool Blue"也會一併被刪除。
foreignKey: 指向關聯model的外部鍵名稱。
如果沒有依照Cake的命名規則建立資料表,就必須在此設定外部鍵的名稱。
現在執行User的find()或findAll()函式,可以看到Profile model也跟著被撈出相關的資料。
$user = $this->User->read(null, '25');
print_r($user);
//輸出:
Array
(
[User] => Array
(
[id] => 25
[first_name] => John
[last_name] => Anderson
[username] => psychic
[password] => c4k3roxx
)
[Profile] => Array
(
[id] => 4
[name] => Cool Blue
[header_color] => aquamarine
[user_id] = 25
)
)定義與使用belongsTo查詢
現在User可以看到它的Profile了。
接著再建立另一種關聯,讓Profile也能看到User。
這就是belongsTo在Cake中所發揮的功能。
我們接著在Profile model裡做這件事:
/app/models/profile.php belongsTo
<?php
class Profile extends AppModel
{
var $name = 'Profile';
var $belongsTo = array('User' =>
array('className' => 'User',
'conditions' => '',
'order'
=> '',
'foreignKey' =>
'user_id'
)
);
}
?>$belongsTo陣列就是Cake用來建立User和Profile model間關聯的關鍵。
裡頭的每個選項可以用來更仔細的設計此關聯。
className (必須):想要建立關聯的model名稱
以我們的例子為例,這個值就是'User'。
conditions:用SQL語法陳述此關係成立的額外限制
用這個設定值告訴Cake只有在User啟用時,此關聯才成立。
此時這個值要設定成"User.active = '1'"或類似的樣子。
order: model的排序規則
如果想要讓model依特定的方式排序,就用SQL命定陳述規則,放在這裡,例如:"User.last_name ASC"。
foreignKey:指向關聯model的外部鍵
若沒有依Cake的命名規則建立資料表,就必須在此設定外部鍵名稱。
現在我們再執行一次Profile的file()或findAll(),就可以很明顯的看到User model也一起被查詢出相關資料:
$profile = $this->Profile->read(null, '4');
print_r($profile);
//output:
Array
(
[Profile] => Array
(
[id] => 4
[name] => Cool Blue
[header_color] => aquamarine
[user_id] = 25
)
[User] => Array
(
[id] => 25
[first_name] => John
[last_name] => Anderson
[username] => psychic
[password] => c4k3roxx
)
)定義與使用hasMany查詢
現在,已經將User和Profile二個model建立起關聯,且運作正常,接下來試著為User和Comment間建立關聯。
結果,User model看起來會像這樣:
/app/models/user.php hasMany
<?php
class User extends AppModel
{
var $name = 'User';
var $hasMany = array('Comment' =>
array('className' =>
'Comment',
'conditions'
=> 'Comment.moderated = 1',
'order'
=> 'Comment.created DESC',
'limit'
=> '5',
'foreignKey'
=> 'user_id',
'dependent'
=> true,
'exclusive'
=> false,
'finderQuery'
=> ''
)
);
// 這是我們先前建立的另一個關聯
var $hasOne = array('Profile' =>
array('className' => 'Profile',
'conditions' => '',
'order'
=> '',
'dependent' =>
true,
'foreignKey' =>
'user_id'
)
);
}
?>$hasMany陣列就是Cake建立User和Comment間關聯的關鍵。陣列內的每個項目可以
讓你更仔細的設計關聯:
className (必須): 想要建立關聯的model名稱
以我們的例子為例,這個值就是'Comment'。
conditions: 用SQL語法陳述此關係成立的額外限制
若想告訴Cake只找出"溫合"的意見,只要把這個值設成"Comment.moderated = 1"或類似的值。
order: model的排序規則
如果想讓關聯資料表以特定的方式排序,可以將SQL指令敍述放在這裡,例如:"Comment.created DESC"。
limit:Cake從關聯資料表撈出的資料數上限
例如,我們不想看所有人的見意,只想看5條。
foreignKey: 指向關聯model的外部鍵
若沒有依Cake的命名規則建立資料表,就必須在此設定外部鍵名稱。
dependent: 如果設成true,則在這個資料被刪時,關聯的資料也會一併被刪。
例如,假設"Cool Blue"關聯於"Bob",當我把"Bob"刪除時,"Cool Blue"也會一起被刪掉。
exclusive: 如果設成true,則關聯物件被刪除前,不會呼叫beforeDelete回呼涵數。
對簡單的關聯非常有用,可以加快執行速度。
finderQuery: 用SQL指令建立關聯
這兒可以對多個資料表進行複雜的關聯。如果Cake自動做的關聯不合你用,就在這兒自己寫一個吧。
現在執行User model的find()或findAll()看看,應該會發現相關聯的Comment model也一併被撈出資料了:
$user = $this->User->read(null, '25');
print_r($user);
//output:
Array
(
[User] => Array
(
[id] => 25
[first_name] => John
[last_name] => Anderson
[username] => psychic
[password] => c4k3roxx
)
[Profile] => Array
(
[id] => 4
[name] => Cool Blue
[header_color] => aquamarine
[user_id] = 25
)
[Comment] => Array
(
[0] => Array
(
[id] => 247
[user_id] => 25
[body] => The hasMany assocation is nice to have.
)
[1] => Array
(
[id] => 256
[user_id] => 25
[body] => The hasMany assocation is really nice to have.
)
[2] => Array
(
[id] => 269
[user_id] => 25
[body] => The hasMany assocation is really, really nice to have.
)
[3] => Array
(
[id] => 285
[user_id] => 25
[body] => The hasMany assocation is extremely nice to have.
)
[4] => Array
(
[id] => 286
[user_id] => 25
[body] => The hasMany assocation is super nice to have.
)
)
)雖然這理並沒有提及詳細的步驟,但最好同時定義"Comment bedongsTo User"的關聯,讓二個model可以互相看到。
當使用scaffold時就常常會沒有定義雙向的關聯。
定義與使用hasAndBelongsToMany
現在應該已經對這簡單的關聯機制很熟析了。
讓我們看看最後一種關聯:hasAndBelongsToMan(或簡稱HABTM)。
最後一種是最難理解的,但它也是最常用的的關聯。
當有二個資料表靠一個Join資料表連結在一起時,HABTM關聯立即發揮它的功用。
Join資料表通常只有二欄,分別關聯到二個資料表。
hasMany和hasAndBelongsToMany的相異處在於hasMany的關連性無法共享。
例如,一個使用者(User)有很多(hasMany)建議(comment),這樣的程式只能讓Comment資料與單一筆User資料產生關聯,
若用HABTM,Comment的資料可以和多筆User資料產生關聯。
馬上舉個例子看看:建立Post和Tag二個model的關聯。
如果一個Tag只能讓一個Post使用,Tag馬上就會用完,但我們不想這様,我們想要讓一個Tag可以讓多個Post使用。
為達此需求,我們必須先設計好正確的資料表。
所以,我們需要為Tag model建立"tags"資料表,為Post model建立"posts"資料表。
接下來要為他們建立一個join資料表,這是一個新的資料表,
Cake對這種資料表的命名規則為[第一組model名稱的複數]_[第二組model名稱的複數],model名稱要依字母排放:
HABTM Join資料表: 範例model和他們的join資料表名稱
Posts 和Tags: posts_tags
Monkeys 和 IceCubes: ice_cubes_monkeys
Categories 和 Articles: articles_categories
HABTM Join資料表最少需為連結的model建立二個外部鍵。以我們的例子來說就是"post_id"和"tag_id"。
要建立這三個資料表的SQL指令如下:
--
-- posts資料表的結構
--
CREATE TABLE `posts` (
`id` int(10) unsigned NOT NULL auto_increment,
`user_id` int(10) default NULL,
`title` varchar(50) default NULL,
`body` text,
`created` datetime default NULL,
`modified` datetime default NULL,
`status` tinyint(1) NOT NULL default '0',
PRIMARY KEY (`id`)
) TYPE=MyISAM;
-- --------------------------------------------------------
--
-- posts_tags資料表的結構
--
CREATE TABLE `posts_tags` (
`post_id` int(10) unsigned NOT NULL default '0',
`tag_id` int(10) unsigned NOT NULL default '0',
PRIMARY KEY (`post_id`,`tag_id`)
) TYPE=MyISAM;
-- --------------------------------------------------------
--
-- tags資料表的結構
--
CREATE TABLE `tags` (
`id` int(10) unsigned NOT NULL auto_increment,
`tag` varchar(100) default NULL,
PRIMARY KEY (`id`)
) TYPE=MyISAM;資料表建定好後就可以在Post model內定義關聯了:
/app/models/post.php hasAndBelongsToMany
<?php
class Post extends AppModel
{
var $name = 'Post';
var $hasAndBelongsToMany = array('Tag' =>
array('className'
=> 'Tag',
'joinTable' => 'posts_tags',
'foreignKey' => 'post_id',
'associationForeignKey'=> 'tag_id',
'conditions' => '',
'order' => '',
'limit' => '',
'unique' => true,
'finderQuery' => '',
'deleteQuery' => '',
)
);
}
?>$hasAndBelongsToMany陣列是Cake用來建立Post和Tag關聯的關鍵。每個項目可以用來更仔細的設定這個關聯的內容:
className (必要):想要建立關聯的model名稱
在我們這個例子中要設為'Tag'
joinTable:當資料庫沒照Cake命名規則建立時,就必須在此指定資料表名稱。
foreignKey:Join資料表內指向目前model的外部鍵名稱。
當資料庫沒照Cake命名規則建立時,就必須在此指定外部鍵名稱。
associationForeignKey:指向關聯資料表的外部鍵名稱。
conditions: 用SQL語法陳述此關係成立的額外限制
我們可以使用這個項目告訴Cake只找Tag資料表中有確認過的標簽籤。
這時要把這個值設定成"Tag.approved = 1"或類似的樣子。
order: model的排序規則
這個項目可以讓關聯model以特定的方式排列。得用SQL語法敍述,例如:"Tag.tag DESC"。
limit: 從關聯資料表中撈出的資料量上限
用來限制撈出的資料筆數上限。
unique: 如果設成true,則忽略重復的資料
基本上,如果關聯很明確,就可以把它設成true。
那麼"Awesomeness"就只能被指定到"Cake Model Associations"一次,只會在結果中出現一個。
finderQuery: 用完整的SQL指令描述關聯
這兒可以對多個資料表進行複雜的關聯。
如果Cake自動做的關聯不合你用,就在這兒自己寫一個吧。
deleteQuery: 用來刪除HABTM model間關聯資料的完整SQL指令
如果你不喜歡Cake的刪除行為,可以在此建立自己的方法來刪除。
當我們現在執行Post model裡的find()或findall(),應該看到Tag model同時也被撈出資料了:
$post = $this->Post->read(null, '2');
print_r($post);
//output:
Array
(
[Post] => Array
(
[id] => 2
[user_id] => 25
[title] => Cake Model Associations
[body] => Time saving, easy, and powerful.
[created] => 2006-04-15 09:33:24
[modified] => 2006-04-15 09:33:24
[status] => 1
)
[Tag] => Array
(
[0] => Array
(
[id] => 247
[tag] => CakePHP
)
[1] => Array
(
[id] => 256
[tag] => Powerful Software
)
)
)儲存相關聯的Model資料
使用關聯式model時有個重點要緊記在心:
所有相關聯的model內,只要其中一個進行儲存,其他的也會跟著儲存。
例如現在要儲存Post和關聯的Comment,想透過Post或Comment model儲存都可以。
如果相關聯的model內其中一個資料還沒建立(例如,我們想同時存入新的Post和它的註解),則必須先存主(或父)model。
舉個例子,相像一下在PostController裡有個動作是儲存新的Post和相關的Comment。
下面的動作會假設你已經發表一個單一的Post和單一個Comment。
/app/controllers/posts_controller.php (部分)
function add()
{
if (!empty($this->data))
{
//我們可以存Post資料:
//它應該會被放在$this->data['Post']
$this->Post->save($this->data);
//現在我們需要儲存Comment資料
//但我們必須先得知剛剛存入的Post的id
$post_id = $this->Post->getLastInsertId();
//然後把這個資料加入欲儲存的資料中
//並且儲存comment
$this->data['Comment']['post_id'] = $post_id;
//因為Post有很多(hasMany)Comments(他們有關聯),我們可以
//透過Post model存取Comment model:
$this->Post->Comment->save($this->data);
}
}然而,如果父model已經存於系統中(例如,現在要在已經存在的Post上再加一個Comment),
在儲存前需要先知道這個Post在父model內的ID值。
你可以透過URL參數(或當作表單中一個隱藏的元件)把ID傳過來。
/app/controllers/posts_controller.php (部分)
//如果用URL參數傳遞,則看起來會長這様
function addComment($post_id)
{
if (!empty($this->data))
{
//你可能會覺得這麼做對$post_id的安全性不高,
//其實這麼做只是為了做示範,夠用了..
$this->data['Comment']['post_id'] = $post_id;
//因為我們的Post和Comment間有hasMany的關聯,所以可以直
//接透過Post model存取Comment model:
$this->Post->Comment->save($this->data);
}
}如果透過表單中隱藏欄位傳遞資訊,那麼先幫欄位取個名字(如果是使用HtmlHelper),它就會出現在POST資料裡應該出現的地方:
如果post的ID是在$post['Post']['id']...
<?php echo $html->hidden('Comment/post_id', array('value' => $post['Post']['id'])); ?>透過這樣的方式,Post資料的ID自動被填到$this->data['Comment']['post_id']裡,
儲存時就只需呼叫$this->Post->Comment->save($this->data)。
同樣的技巧也可以用在儲存多個子model,只要把這些save()的呼叫放在迴圈中(記得使用Model::Create()清除model資訊)。
總而言之,如果要儲存相關聯的資料(對belongsTo,hasOne,和hasMany來說),就是要先取得資料在父model的ID值,再存到子model中。
儲存 hasAndBelongsToMany 的關聯資料
儲存具有hasOne、belongsTo和hasMany關聯的資料十分容易:
只要把外部鍵和ID值開放,結束時就於model裡呼叫save()即可,所有事都會自動連結好。
但如果是儲存具有hasAndBelongsToMany的資料,就有點技巧,但我們會用我們的方法讓它盡可能的簡單。
跟著我們的範例走前,必須先做一些資料表,建立Tags與Posts的關聯。
先建立一個用來新增post的表單,然後把他們關聯到一組已存在的Tag上。
或許你想建一個可以新增Tag然後動態進行關聯動作的表單。
但為了讓事情單純化,這裡只示範如果進行關聯動作,其他的留給你做。
在Cake中讓model自己儲存資料的話,tag的名字看起來會類似'Model/field_name'(如果你是使用Html Helper)。
我們直接從新增文章的表單那部分開始說:
/app/views/posts/add.thtml 新增文章的表單
<h1>Write a New Post</h1>
<table>
<tr>
<td>Title:</td>
<td><?php echo $html->input('Post/title')?></td>
</tr>
<tr>
<td>Body:<td>
<td><?php echo $html->textarea('Post/body')?></td>
</tr>
<tr>
<td colspan="2">
<?php echo
$html->hidden('Post/user_id',
array('value'=>$this->controller->Session->read('User.id')))?>
<?php echo $html->hidden('Post/status' , array('value'=>'0'))?>
<?php echo $html->submit('Save Post')?>
</td>
</tr>
</table>
這樣的表單只能新增一筆文章資料。再加上一些程式碼,可以在文章上加一個或多個標籤:
/app/views/posts/add.thtml (加入了標籤相關的程式碼)
<h1>Write a New Post</h1>
<table>
<tr>
<td>Title:</td>
<td><?php echo $html->input('Post/title')?></td>
</tr>
<tr>
<td>Body:</td>
<td><?php echo $html->textarea('Post/body')?></td>
</tr>
<tr>
<td>Related Tags:</td>
<td><?php echo
$html->selectTag('Tag/Tag', $tags, null, array('multiple' =>
'multiple')) ?>
</td>
</tr>
<tr>
<td colspan="2">
<?php echo
$html->hidden('Post/user_id',
array('value'=>$this->controller->Session->read('User.id')))?>
<?php echo $html->hidden('Post/status' , array('value'=>'0'))?>
<?php echo $html->submit('Save Post')?>
</td>
</tr>
</table>
為了在呼叫controller裡的$this->Post->save()後可以把新的文章和關聯的標籤存起來,
欄位名稱要叫作"Tag/Tag"(欄位屬性會看起來像'data[ModelName][ModelName][]')。
傳回的資料必須是單獨一個ID或一組ID的陣列。因為這裡我們使用多選元件,傳回的結果會是一個ID的陣列。
$tags變數是一個陣列,內部的鍵是所有可能的標籤ID,值會在多選選單元件上顯示標籤的名稱。
使用bindModel()和unbindModel()動態改變關聯
有時候我們會想在某些狀況時動態改變資料間的關聯性。
如果你覺得model檔裡定義了太多關聯的資訊,可以用bindModel和unbindModel為下一個查詢呼叫時建立或解除關聯性。
我們先做二個model來示簵bindModel()和unbindMode()的運作狀況:
leader.php 和 follower.php
<?php
class Leader extends AppModel
{
var $name = 'Leader';
var $hasMany = array(
'Follower' => array(
'className' => 'Follower',
'order' => 'Follower.rank'
)
);
}
?>
<?php
class Follower extends AppModel
{
var $name = 'Follower';
}
?>在LeadersController中可以用Leader Model的find()方法找到一個領袖和他的追隨者。
如上面所示,Leader Model裡定義了一組關聯陣列,說明了"領袖有很多追隨者"。
為了示範,讓我們用unbindModel()解除這個領袖的追隨者。
leaders_controller.php (部分)
function someAction()
{
//這裡會撈出領袖和追隨者的資料
$this->Leader->findAll();
//移除hasMany關聯...
$this->Leader->unbindModel(array('hasMany' => array('Follower')));
//現在再查詢一次,就只會傳回一個沒有追隨者的領袖
$this->Leader->findAll();
//註: unbindModel只會影響下一個查詢函式
//接下來的查詢函式會自動恢復原始設定
//我們已經在unbindModel()後呼叫過一次findAll(), 所以
//這次查詢結果會再度撈出領袖和他的追隨者...
$this->Leader->findAll();
}將unbindModel函式用在其他種類的關聯上的方法也都類似:
只要改變關聯名稱和model的名稱就行了。unbindModel()的基本用法如下:
通用的 unbindModel() 範例
$this->Model->unbindModel(array('associationType' => array('associatedModelClassName')));現在已經成功的動態解除關聯,接下來讓我們動態加上一個關聯。
這個看起來沒有什麼信條的領袖,需要加上一些信條。
Principle model的程式碼很簡單,裡頭什麼都沒有,只有一行指定$name的程式。
現在就動態為領袖加上一些信條(一樣只有下一個查詢有用):
leaders_controller.php (部分)
funciton anotherAction()
{
//在leader.php的model檔中沒有在Leader和Priciple間建立hasMan關聯,
//所以這個查詢動作只會撈出領袖資料
$this->Leader->findAll();
//用bindModel()在Leader和Priciple間建立hasMany的關聯
$this->Leader->bindModel(
array('hasMany' => array(
'Principle' => array(
'className' => 'Principle'
)
)
)
);
//現在已經建立好關聯,在下一個查詢動作中可以同時撈出領袖和
//他的信條
$this->Leader->findAll();
}bindModel()可以輕易的加入新的關聯,如果想要改變排列規則或其他參數也很有用。
現在你已了解,bindModel的基本用法就是把一般的關聯陣列包在另一個鍵是關聯名稱的陣列中:
通用的bindModel()範例
$this->Model->bindModel(
array('associationName' => array(
'associatedModelClassName' => array(
// normal association keys go here...
)
)
)
);請注意動態設定時,必須確保執行時資料是正確的。
沒有留言:
張貼留言