保存相关模型的数据(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。