保存相关模型数据 (HABTM)

通过 hasOne、belongsTo、hasMany 保存有关联的模型是非常简单的: 只需要将关联模型的 ID 填入外键列。 填完之后,只要调用模型上的 save() 方法,一切就都被正确的串连起来了。 下面是准备传递给 Tag 模型的 save() 方法的数据数组格式的示例:

 1 Array
 2 (  3     [Recipe] => Array
 4         (  5             [id] => 42
 6         )  7     [Tag] => Array
 8         (  9             [name] => Italian 10         ) 11 )

也可以在 saveAll() 中使用这种格式保存多条记录和与它们有 HABTM 关联的的模型,格式如下:

 1 Array
 2 (  3     [0] => Array
 4         (  5             [Recipe] => Array
 6                 (  7                     [id] => 42
 8                 )  9             [Tag] => Array
10                 ( 11                     [name] => Italian 12                 ) 13         ) 14     [1] => Array
15         ( 16             [Recipe] => Array
17                 ( 18                     [id] => 42
19                 ) 20             [Tag] => Array
21                 ( 22                     [name] => Pasta 23                 ) 24         ) 25     [2] => Array
26         ( 27             [Recipe] => Array
28                 ( 29                     [id] => 51
30                 ) 31             [Tag] => Array
32                 ( 33                     [name] => Mexican 34                 ) 35         ) 36     [3] => Array
37         ( 38             [Recipe] => Array
39                 ( 40                     [id] => 17
41                 ) 42             [Tag] => Array
43                 ( 44                     [name] => American (new) 45                 ) 46         ) 47 )

将上面的数组传递给 saveAll() 将创建所包含的 tag ,每个都与它们各自的 recipe 关联。

作为示例,我们建立了创建新 tag 和运行期间生成与 recipe 关联的正确数据数组的表单。

这个简单的表单如下:(我们假定 $recipe_id 已经设置了):

1 <?php echo $this->Form->create('Tag'); ?>
2 <?php echo $this->Form->input( 3         'Recipe.id',
4         array('type' => 'hidden', 'value' => $recipe_id) 5     ); ?>
6 <?php echo $this->Form->input('Tag.name'); ?>
7 <?php echo $this->Form->end('Add Tag'); ?>

在这个例子中,你能看到 Recipe.id hidden 域,其值被设置为我们的 tag 想要连接的 recipe 的 ID。

当在控制器中调用 save() 方法,它将自动将 HABTM 数据保存到数据库:

1 public function add() { 2     // 保存关联
3     if ($this->Tag->save($this->request->data)) { 4         // 保存成功后要做的事情
5     } 6 }

这段代码将创建一个新的 Tag 并与 Recipe 相关联,其 ID 由 $this->request->data['Recipe']['id'] 设置。

某些情况下,我们可能希望呈现的关联数据能够包含下拉 select 列表。数据可能使用 find('list') 从模型中取出并且赋给用模型名命名的视图变量。 同名的 input 将自动把数据放进 <select> :

1 // 控制器中的代码:
2 $this->set('tags', $this->Recipe->Tag->find('list'));
1 // 视图中的代码:
2 $this->Form->input('tags');

更可能的情形是一个 HABTM 关系包含一个允许多选的 <select>。例如,一个 Recipe 可能被赋了多个 Tag。在这种情况下,数据以相同的方式从模型中取出,但是表单 input 定义稍有不同。tag 的命名使用 ModelName 约定:

1 // 控制器中的代码:
2 $this->set('tags', $this->Recipe->Tag->find('list'));
1 // 视图中的代码:
2 $this->Form->input('Tag');

使用上面这段代码,将建立可多选的下拉列表(select),允许多选自动被保存到已添加或已保存到数据库中的 Recipe。

当 HABTM 变得复杂时怎么办?

默认情况下,Cake 在保存 HABTM 关系时,会先删除连接表中的所有行。 例如,有一个拥有10个 Children 关联的 Club。带着2个 children 更新 Club。Club 将只有2个 Children,而不是12个。

要注意,如果想要向带有 HABTM 的连接表添加更多的列(建立时间或者元数据)是可能的,重要的是要明白你有一个简单的选项。

两个模型间的 HasAndBelongsToMany 关联实际上是同时拥有 hasMany 和 belongsTo 关联的三个模型关系的简写。

考虑下面的例子:

Child hasAndBelongsToMany Club

另一个方法是添加一个 Membership 模型:

Child hasMany Membership
Membership belongsTo Child, Club
Club hasMany Membership.

这两个例子几乎是相同的。它们在数据库中使用了命名相同的 amount 列,模型中的 amount 也是相同的。最重要的不同是 “join” 表命名不同,并且其行为更具可预知性。

小技巧

当连接表包含外键以外的扩展列时,通过将数组的 'unique' 设置为 “‘keepExisting’”,能够防止丢失扩展列的值。同样,可以认为设置 ‘unique’ => true,在保存操作过程中不会丢失扩展列的数据。参见 HABTM association arrays

不过,更多情况下,为连接表建立一个模型,并像上面的例子那样设置 hasMany、belongsTo 关联,代替使用 HABTM 关联,会更简单。

数据表

虽然 CakePHP 可以有非数据库驱动的数据源,但多数时候,都是有数据库驱动的。 CakePHP 被设计成可以与 MySQL、MSSQL、Oracle、PostgreSQL 和其它数据库一起工作。 你可以创建你平时所用的数据库系统的表。在创建模型类时,模型将自动映射到已经建立的表上。表名被转换为复数小写,多个单词的表名的单词用下划线间隔。例如,名为 Ingredient 的模型对应的表名为 ingredients。名为 EventRegistration 的模型对应的表名为 event_registrations。CakePHP 将检查表来决定每个列的数据类型,并使用这些信息自动化各种特性,比如视图中输出的表单域。列名被转换为小写并用下划线间隔。

使用 created 和 modified 列

通过在数据库表中定义 created 和 modified 列作为 datetime 列,CakePHP 能够识别这些域并自动在其中填入记录在数据库中创建的时间和保存的时间(除非被保存的数据中已经包含了这些域的值)。

在记录最初添加时,created 和 modified 列将被设置为当前日期和时间。当已经存在的记录被保存时,modified 列将被更新至当前日期和时间。

如果在 Model::save() 之前 $this->data 中包含了 updated、created、modified 数据(例如 Model::read 或者 Model::set),那么这些值将从 $this->data 中获取,并且不自动更新。 或者使用 unset($this->data['Model']['modified']) 等方法。总是可以覆盖 Model::save() 方法来做这件事:

 1 class AppModel extends Model {  2 
 3     public function save($data = null, $validate = true, $fieldList = array()) {  4         // 在每个保存操作前清除 modified 域值:
 5         $this->set($data);  6         if (isset($this->data[$this->alias]['modified'])) {  7             unset($this->data[$this->alias]['modified']);  8         }  9         return parent::save($this->data, $validate, $fieldList); 10     } 11 
12 }