保存数据
CakePHP 会为保存模型数据制作快照。准备保存的数据使用如下基本格式传递给模型的 save() 方法:
1 Array
2 (
3 [ModelName] => Array
4 (
5 [fieldname1] => 'value'
6 [fieldname2] => 'value'
7 )
8 )
多数时候你无需担心这种格式: CakePHP 的 FormHelper 和模型的 find 方法都用这种格式打包所有数据。如果使用其它的助手,数据也能方便地以 $this->request->data 形式使用。
下面是使用 CakePHP 模型向数据库表存入数据的控制器动作的示例:
1 public function edit($id) {
2 // 有表单数据被 POST?
3 if ($this->request->is('post')) {
4 // 如果表单数据能够通过校验并保存...
5 if ($this->Recipe->save($this->request->data)) {
6 // 设置 session 跳转信息并跳转
7 $this->Session->setFlash('Recipe Saved!');
8 $this->redirect('/recipes');
9 }
10 }
11
12 // 如果没有表单数据,查找被编辑的 recipe 并将其赋值给视图。
13 $this->set('recipe', $this->Recipe->findById($id));
14 }
在 save 方法被调用时,在第一个参数中传递给它的数据,被 CakePHP 校验机制校验(更多信息请参见 数据校验 一节)。 如果因为某些原因,数据没有被保存,检查一下是不是没有符合某些校验规则。 可以通过输出Model::$validationErrors 来 debug 这种情况。
1 if ($this->Recipe->save($this->request->data)) {
2 // "保存" 成功后的处理逻辑
3 }
4 debug($this->Recipe->validationErrors);
其它一些与保存相关的有用的模型方法:
Model::set($one, $two = null)
Model::set() 能够用于将数据的一个或多个列放入模型的 data 数组。当使用带有由 Model 提供的 ActiveRecord 特性的模型时很有用:
1 $this->Post->read(null, 1);
2 $this->Post->set('title', 'New title for the article');
3 $this->Post->save();
此例展示了如何使用 ActiveRecord 的 set() 方法更新和保存单个列。还可以使用 set() 给多个列赋新值:
1 $this->Post->read(null, 1);
2 $this->Post->set(array(
3 'title' => 'New title',
4 'published' => false
5 ));
6 $this->Post->save();
上例将更新 thitle 和 published 列并保存到数据库中。
Model::save(array $data = null, boolean $validate =true, array $fieldList = array())
这个方法保存数组格式的数据。第二个参数允许跳过校验,第三个参数允许提供要保存的模型的列的列表。为了提高安全性,可以使用 $fieldList 限制要保存的列。
注解
如果不提供 $fieldList,恶意用户能够向表单数据中添加附加的列(在你没有使用 SecurityComponent 的情况下),并通过这种方法来改变原本不可以被改变的列。
save 方法还有一个替代语法:
1 save(array $data = null, array $params = array())
$params 数组可以用如下选项作为其键:
validate 设置为 true/false 能够 允许/禁止 校验。
fieldList 允许保存的列构成的数组。
callbacks 设置为 false 将禁止回调。使用 ‘before’ 或 ‘after’ 将仅允许指定的回调。
关于模型回调的更多信息请参见 这里
小技巧
如果你不想更新列在保存某些数据时被更新,在 $data 数组中添加 'updated' => false。
一旦保存完成,可以使用模型对象的 $id 属性获得对象的 ID - 在创建新对象时可能会非常有用。
1 $this->Ingredient->save($newData);
2 $newIngredientId = $this->Ingredient->id;
创建或更新是通过模型的 id 列来控制的。如果设置了 $Model->id,带有这个主键的记录将被更新。 其它情况下,一条新记录被创建:
1 // 创建新记录: id 没有设置或设置为 null
2 $this->Recipe->create();
3 $this->Recipe->save($this->request->data);
4
5 // 更新记录: id 被设置为一个数字值
6 $this->Recipe->id = 2;
7 $this->Recipe->save($this->request->data);
小技巧
在循环中调用 save 时,不要忘记调用 create() 。
如果想更新一个值,而不是创建一条新记录,必须确保向数据数组传递了主键列:
1 $data = array('id' => 10, 'title' => 'My new title');
2 // 将更新 id 为 10 的 Recipe 记录
3 $this->Recipe->save($data);
Model::create(array $data = array())
这个方法为保存新信息重置模型的状态。 实际上它并不在数据库中创建新记录,而是清除预先设置的 Model::$id,并在 Model::$data 中设置基于数据库列默认的默认值。
如果传递了 $data 参数(使用上面描述的数组格式),模型实例将准备保存这些数据(使用 $this->data)。
如果用 false 代替一个数组传递给此方法,模型实际将不根据之前没有设置的模型结构来初始化列,而仅仅重置已经设置的列, 并且保留未设置的列。 这么做是为了避免更新数据库中已经设置的列的值。
小技巧
如果想要用插入一个新行来代替更新已经存在的一行,必须先调用 create()。这样能够避免与回调或者其它位置中曾调用过的 save 发生冲突。
Model::saveField(string $fieldName, string$fieldValue, $validate = false)
用于保存单个列的值。在使用 saveField() 之前要先设置模型的 ID ($this->ModelName->id = $id)。在使用这个方法时,$fieldName 仅需要包含列名,不需要模型名和列。
例如,更新一条博客的标题,可以用如下方式在控制器中调用 saveField:
1 $this->Post->saveField('title', 'A New Title for a New Day');
警告
在使用这个方法更新时不能停止更新列,你需要使用 save() 方法。
saveField 方法也有一个替代语法:
1 saveField(string $fieldName, string $fieldValue, array $params = array())
$params 数组可以用如下选项作为其键:
validate 设置为 true/false 能够 允许/禁止 校验。
callbacks 设置为 false 将禁止回调。使用 ‘before’ 或 ‘after’ 将仅允许指定的回调。
Model::updateAll(array $fields, array $conditions)
一次调用更新一条或多条记录。被更新的记录通过 $conditions 数组标识,$fields 参数指定的列和值被更新。
例如,批准所有成为会员超过一年的面包师,调用如下的更新语句:
1 $this_year = date('Y-m-d h:i:s', strtotime('-1 year'));
2
3 $this->Baker->updateAll(
4 array('Baker.approved' => true),
5 array('Baker.created <=' => $this_year)
6 );
小技巧
$fields 数组接受 SQL 表达式。字面值使用 Sanitize::escape() 手动引用。
注解
即使列中存在的编辑列被更新,它也不会通过 ORM 自动更新。必须手动将其加入到你想更新的数组中。
例如,关闭所有属于指定客户的所有门票:
1 $this->Ticket->updateAll(
2 array('Ticket.status' => "'closed'"),
3 array('Ticket.customer_id' => 453)
4 );
默认情况下,updateAll() 将自动连接支持 join 的数据库的 belongsTo 关联。通过临时绑定关联能够防止这种连接。
Model::saveMany(array $data = null, array $options= array())
此方法用于同时保存同一模型的多行。可以带有如下选项:
validate: 设置为 false 将禁止校验,设置为 true 将在保存前校验每条记录,设置为 ‘first’(此为默认值) 将在任意一条被保存前检查 全部 记录。
atomic: 如果为 true(默认),将在单个指令中保存所有记录,如果 数据库/表 不支持单指令需要设置为 false。
fieldList: 与 Model::save() 方法的 $fieldList 参数相同。
deep: (自 2.1 版开始)如果设置为 true,关联数据也被保存,参见 saveAssociated。
为单个模型保存多条记录,$data 需要是数字索引的记录数组:
1 $data = array(
2 array('title' => 'title 1'),
3 array('title' => 'title 2'),
4 );
注解
我们传递了数字索引代替了通常情况下 $data 包含的 Article 键。在保存同一模型的多条记录时,记录数组需要使用数字索引,而不是模型的键。
它也可以接受如下格式的数据:
1 $data = array(
2 array('Article' => array('title' => 'title 1')),
3 array('Article' => array('title' => 'title 2')),
4 );
如果还要保存带有 $options['deep'] = true 的关联数据,上面的两个例子将类似于下面的代码:
1 $data = array(
2 array('title' => 'title 1', 'Assoc' => array('field' => 'value')),
3 array('title' => 'title 2'),
4 );
5 $data = array(
6 array('Article' => array('title' => 'title 1'), 'Assoc' => array('field' => 'value')),
7 array('Article' => array('title' => 'title 2')),
8 );
9 $Model->saveMany($data, array('deep' => true));
切记,如果想用更新记录代替创建新记录,需要向数据行添加主键索引:
1 $data = array(
2 array('Article' => array('title' => 'New article')), // 创建新记录
3 array('Article' => array('id' => 2, 'title' => 'title 2')), // 更新存在的记录
4 );
Model::saveAssociated(array $data = null, array$options = array())
此方法用于一次保存多个模型关联。可以带有如下选项:
validate: 设置为 false 将禁止校验,设置为 true 将在保存前校验每条记录,设置为 ‘first’(此为默认值) 将在任意一条被保存前检查 全部 记录。
atomic: 如果为 true(默认),将在单个指令中保存所有记录,如果 数据库/表 不支持单指令需要设置为 false。
fieldList: 与 Model::save() 方法的 $fieldList 参数相同。
deep:(自 2.1 版开始)如果设置为 true,关联数据也被保存,参见 saveAssociated。
为了保存记录的同时保存与其有着 hasOne 或者 belongsTo 关联的记录,data 数组看起来就像下面这样:
1 $data = array(
2 'User' => array('username' => 'billy'),
3 'Profile' => array('sex' => 'Male', 'occupation' => 'Programmer'),
4 );
为了保存记录的同时,保存与其有着 hasMany 关联的记录,data 数组看起来就像下面这样:
1 $data = array(
2 'Article' => array('title' => 'My first article'),
3 'Comment' => array(
4 array('body' => 'Comment 1', 'user_id' => 1),
5 array('body' => 'Comment 2', 'user_id' => 12),
6 array('body' => 'Comment 3', 'user_id' => 40),
7 ),
8 );
为了保存记录的同时保存与其有着超过两层深度的 hasMany 关联的记录,data 数组看起来就像下面这样:
1 $data = array(
2 'User' => array('email' => 'john-doe@cakephp.org'),
3 'Cart' => array(
4 array(
5 'payment_status_id' => 2,
6 'total_cost' => 250,
7 'CartItem' => array(
8 array(
9 'cart_product_id' => 3,
10 'quantity' => 1,
11 'cost' => 100,
12 ),
13 array(
14 'cart_product_id' => 5,
15 'quantity' => 1,
16 'cost' => 150,
17 )
18 )
19 )
20 )
21 );
注解
如果保存成功,主模型的外键将被存储在相关模型的 id 列中,例如 $this->RelatedModel->id。
警告
在调用 atomic 选项设置为 false 的 saveAssociated 方法时要小心的进行检查,它返回的是一个数组,而不是逻辑值。
在 2.1 版更改: 现在你可以保存深层关联的数据(用 $options['deep'] = true 设置)。
为了保存记录的同时,保存与其有 hasMany 关联的相关记录及深层关联的 Comment belongsTo User 数据,data 数组看起来就像下面这样::
1 $data = array(
2 'Article' => array('title' => 'My first article'),
3 'Comment' => array(
4 array('body' => 'Comment 1', 'user_id' => 1),
5 array('body' => 'Save a new user as well', 'User' => array('first' => 'mad', 'last' => 'coder')),
6 ),
7 );
并用如下语句进行保存:
1 $Article->saveAssociated($data, array('deep' => true));
在 2.1 版更改: Model::saveAll() 和同族方法现在支持为多个模型传递 fieldList。
为多个模型传递 fieldList 的例子:
1 $this->SomeModel->saveAll($data, array(
2 'fieldList' => array(
3 'SomeModel' => array('field_1'),
4 'AssociatedModel' => array('field_2', 'field_3')
5 )
6 ));
fieldList 是一个以模型别名为键,以列构成的数组作为值的数组。 模型名如同在被保存的数据中那样,不能嵌套。
Model::saveAll(array $data = null, array $options =array())
saveAll 函数只是 savaMany 和 saveAssociated 方法的包装器。它检查数据并且决定执行哪种数据保存类型。它查看数据并决定执行哪种类型的保存。如果数据是数字索引数组,saveMany 被调用,否则 saveAssociated 被调用。
此函数的选项与前面的两个函数相同,并向后兼容。推荐根据实际情况使用 saveMany 或 saveAssociated。
保存相关模型的数据(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 Form->create('CourseMembership'); ?>
21 Form->input('Student.first_name'); ?>
22 Form->input('Student.last_name'); ?>
23 Form->input('Course.name'); ?>
24 Form->input('CourseMembership.days_attended'); ?>
25 Form->input('CourseMembership.grade'); ?>
26
27 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 Form->create('CourseMembership'); ?>
4 Form->input('Student.id', array('type' => 'text', 'label' => 'Student ID', 'default' => 1)); ?>
5 Form->input('Course.id', array('type' => 'text', 'label' => 'Course ID', 'default' => 1)); ?>
6 Form->input('CourseMembership.days_attended'); ?>
7 Form->input('CourseMembership.grade'); ?>
8
9 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。
保存相关模型数据 (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 Form->create('Tag'); ?>
2 Form->input(
3 'Recipe.id',
4 array('type' => 'hidden', 'value' => $recipe_id)
5 ); ?>
6 Form->input('Tag.name'); ?>
7 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 将自动把数据放进