保存相關模型的數據(hasOne, hasMany, belongsTo)
在與關聯模型一起工作時,When working with associated models, 一定要意識到模型數據的保存總是由相應有 CakePHP 模型來完成。如果保存一條新的 Post 和它關聯的 Comment,就需要在保存操作的過程中同時使用 Post 和 Comment 模型。
如果系統中還不存在關聯模型記錄(例如,想要保存新的 User,同時保存相關的 Profile 記錄),需要先保存主模型或者父模型。
爲了瞭解這是如何工作的,想像一下我們在處理保存新用 User 和相關 Profile 的控制器中有一個動作。下面的示例動作假設已經爲創建單個 User 和單個 Profile,POST 了足夠的數據(使用 FormHelper):
1 public function add() { 2 if (!empty($this->request->data)) { 3 // 我們能保存 User 數據: 4 // 它放在 $this->request->data['User'] 中 5 6 $user = $this->User->save($this->request->data); 7 8 // 如果用戶被保存,添加這條信息到數據並保存 Profile。 9 10 if (!empty($user)) { 11 // 新創建的 User ID 已經被賦值給 $this->User->id. 12 $this->request->data['Profile']['user_id'] = $this->User->id; 13 14 // 由於 User hasOne Profile,因此可以通過 User 模型訪問 Profile 模型: 15 $this->User->Profile->save($this->request->data); 16 } 17 } 18 }
作爲一條規則,當帶有 hasOne、hasMany、belongsTo 關聯時,全部與鍵有關。基本思路是從一個模型中獲取鍵,並將其放入另一個模型的外鍵列中。有時需要涉及使用保存後的模型類的 $id 屬性,但是其它情況下只涉及從 POST 給控制器動作的表單的隱藏域(hidden input)中得到的 ID。
作爲上述基本方法的補充,CakePHP 還提供了一個非常有用的方法 saveAssociated(),它允許你用一個簡短的方式校驗和保存多個模型。另外,saveAssociated() 還提供了事務支持以確保數據庫中的數據的完整(例如,一個模型保存失敗,另一個模型也就不保存了)。
註解
爲使事務工作在 MySQL 中正常工作,表必須使用 InnoDB 引擎。記住,MyISAM 表不支持事務。
來看看如何使用 saveAssociated() 同時保存 Company 和 Account 模型吧。
首先,需要同時爲 Company 和 Account 創建表單(假設 Company hasMany Account):
1 echo $this->Form->create('Company', array('action' => 'add')); 2 echo $this->Form->input('Company.name', array('label' => 'Company name')); 3 echo $this->Form->input('Company.description'); 4 echo $this->Form->input('Company.location'); 5 6 echo $this->Form->input('Account.0.name', array('label' => 'Account name')); 7 echo $this->Form->input('Account.0.username'); 8 echo $this->Form->input('Account.0.email'); 9 10 echo $this->Form->end('Add');
看看爲 Acount 模型命名錶單列的方法。如果 Company 是主模型,saveAssociated() 期望相關模型(Account)數據以指定的格式放進數組。並且擁有我們需要的 Account.0.fieldName。
註解
上面的列命名對於 hasMany 關聯是必須的。如果關聯是 hasOne,你就得爲關聯模型使用 ModelName.fieldName 了。
現在,可以在 CompaniesController 中創建 add() 動作了:
1 public function add() { 2 if (!empty($this->request->data)) { 3 // 使用如下方式避免校驗錯誤: 4 unset($this->Company->Account->validate['company_id']); 5 $this->Company->saveAssociated($this->request->data); 6 } 7 }
這就是全部的步驟了。現在 Company 和 Account 模型將同時被校驗和保存。默認情況下,saveAssociated 將檢驗傳遞過來的全部值,然後嘗試執行每一個保存。
通過數據保存 hasMany
讓我們來看看存在在 join 表裏的兩個模型的數據是如何保存的。就像 hasMany 貫穿 (連接模型) 一節展示的那樣,join 表是用 hasMany 類型的關係關聯到每個模型的。 我們的例子包括 Cake 學校的負責人要求我們寫一個程序允許它記錄一個學生在某門課上出勤的天數和等級。下面是示例代碼:
1 // Controller/CourseMembershipController.php 2 class CourseMembershipsController extends AppController { 3 public $uses = array('CourseMembership'); 4 5 public function index() { 6 $this->set('courseMembershipsList', $this->CourseMembership->find('all')); 7 } 8 9 public function add() { 10 if ($this->request->is('post')) { 11 if ($this->CourseMembership->saveAssociated($this->request->data)) { 12 $this->redirect(array('action' => 'index')); 13 } 14 } 15 } 16 } 17 18 // View/CourseMemberships/add.ctp 19 20 <?php echo $this->Form->create('CourseMembership'); ?> 21 <?php echo $this->Form->input('Student.first_name'); ?> 22 <?php echo $this->Form->input('Student.last_name'); ?> 23 <?php echo $this->Form->input('Course.name'); ?> 24 <?php echo $this->Form->input('CourseMembership.days_attended'); ?> 25 <?php echo $this->Form->input('CourseMembership.grade'); ?> 26 <button type="submit">Save</button> 27 <?php echo $this->Form->end(); ?>
提交的數據數組如下:
1 Array 2 ( 3 [Student] => Array 4 ( 5 [first_name] => Joe 6 [last_name] => Bloggs 7 ) 8 9 [Course] => Array 10 ( 11 [name] => Cake 12 ) 13 14 [CourseMembership] => Array 15 ( 16 [days_attended] => 5 17 [grade] => A 18 ) 19 20 )
Cake 會很樂意使用一個帶有這種數據結構的 saveAssociated 調用就能同時保存很多,並將 Student 和 Course 的外鍵賦予 CouseMembership. 如果我們運行 CourseMembershipsController 上的 index 動作,從 find(‘all’) 中獲取的數據結構如下:
1 Array 2 ( 3 [0] => Array 4 ( 5 [CourseMembership] => Array 6 ( 7 [id] => 1 8 [student_id] => 1 9 [course_id] => 1 10 [days_attended] => 5 11 [grade] => A 12 ) 13 14 [Student] => Array 15 ( 16 [id] => 1 17 [first_name] => Joe 18 [last_name] => Bloggs 19 ) 20 21 [Course] => Array 22 ( 23 [id] => 1 24 [name] => Cake 25 ) 26 ) 27 )
當然,還有很多帶有連接模型的工作的方法。上面的版本假定你想要立刻保存每樣東西。 還有這樣的情況:你想獨立地創建 Student 和 Course,稍後再指定兩者與 CourseMembership 的關聯。 因此你可能有一個允許利用列表或ID選擇存在的學生和課程及兩個 CourseMembership 元列的表單,例如:
1 // View/CourseMemberships/add.ctp 2 3 <?php echo $this->Form->create('CourseMembership'); ?> 4 <?php echo $this->Form->input('Student.id', array('type' => 'text', 'label' => 'Student ID', 'default' => 1)); ?> 5 <?php echo $this->Form->input('Course.id', array('type' => 'text', 'label' => 'Course ID', 'default' => 1)); ?> 6 <?php echo $this->Form->input('CourseMembership.days_attended'); ?> 7 <?php echo $this->Form->input('CourseMembership.grade'); ?> 8 <button type="submit">Save</button> 9 <?php echo $this->Form->end(); ?>
所得到的 POST 數據:
1 Array 2 ( 3 [Student] => Array 4 ( 5 [id] => 1 6 ) 7 8 [Course] => Array 9 ( 10 [id] => 1 11 ) 12 13 [CourseMembership] => Array 14 ( 15 [days_attended] => 10 16 [grade] => 5 17 ) 18 )
Cake 利用 saveAssociated 將 Student id 和 Course id 推入 CourseMembership。