保存数据

    大多数时候你根本无需担心这种格式:CakePHP 的 FormHelper 和模型的find 方法都会将数据包装成这种格式。如果使用 ,数据也在$this->request->data 方便地可供立即使用。

    下面是控制器动作使用 CakePHP 模型向数据库表存入数据的简单示例:

    1. public function edit($id) {
    2. // 是否有表单数据被提交(*POSTed*)?
    3. if ($this->request->is('post')) {
    4. // 如果表单数据能够通过验证并保存成功……
    5. if ($this->Recipe->save($this->request->data)) {
    6. // 设置会话(*session*)闪现提示信息并跳转
    7. $this->Session->setFlash('Recipe Saved!');
    8. return $this->redirect('/recipes');
    9. }
    10. }
    11.  
    12. // 如果没有表单数据,查找要编辑的菜单(*recipe*)并将其交给视图
    13. $this->set('recipe', $this->Recipe->findById($id));
    14. }

    当调用 save 方法时,在第一个参数中传入的数据会使用 CakePHP 的验证机制进行验证(欲知详情,请参见 数据验证 一章)。如果因为某些原因,数据没有保存,一定要检查是否是某些验证规则没有通过。这种情况下,可以通过输出Model::$validationErrors 来进行调试:

    1. if ($this->Recipe->save($this->request->data)) {
    2. // 处理成功的情况。
    3. }
    4. debug($this->Recipe->validationErrors);

    模型中还有其它一些与保存相关的方法会对你有用:

    Model::set() 可以用于将一个或多个字段的数据设置到模型的 data 数组中。这可用于把模型和模型提供的 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();

    上例会更新 title 和 published 字段,并把记录保存到数据库中。

    Model::clear()

    该方法可用于重置模型状态,并清除任何未保存数据及验证错误。

    2.4 新版功能.

    Model::save(array $data = null, boolean $validate = true, array $fieldList = array())

    如前所示,这个方法保存数组格式的数据。第二个参数让你可以跳过验证,第三个参数让你可以提供要保存的模型字段列表。为了增强安全性,可以使用 $fieldList 限制要保存的字段。

    注解

    如果不提供 $fieldList,恶意的用户能够向表单数据中添加额外的字段(在你没有使用 的情况下),从而改变原本不希望被改变的字段。

    save 方法还有另外一种语法:

    1. save(array $data = null, array $params = array())

    $params 数组可以使用如下任意选项作为其键:

    • validate 设置为 true/false 来开启/关闭验证。
    • fieldList 允许保存的字段数组。
    • callbacks 设置为 false 将禁止回调。使用 'before' 或 'after' 将仅开启指定的回调。
    • counterCache (从 2.4 版本开始)控制计数器缓存(如果有的话)更新的布尔值
    • atomic (从 2.6 版本开始) 指定要使用事务保存记录的布尔值。
      欲知模型回调的更多信息,请参见 这里

    小技巧

    如果你不希望保存某些数据时自动更新 modified 字段,在 $data 数组中添加'modified' => 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 方法时,不要忘记调用 clear()

    如果想更新一条记录,而不是创建一条新记录,请确保向数据数组传入了主键字段:

    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。如果没有定义数据库字段的默认值,Model::$data 会被设置空数组。

    如果传入了 $data 参数(使用上面描述的数组格式),这会和数据库字段的默认值合并,并准备好模型实例来保存这些数据(可由 $this->data 访问)。

    如果传入 falsenull$data 参数,Model::$data 会被设置为空数组。

    小技巧

    如果要插入一新行而不是更新已存在的一行,应当总是先调用 create()。这样能够在回调函数或者其它地方避免与之前的 save 调用发生冲突。

    用于保存单个字段的值。在将要调用 saveField() 之前要设置模型的 ID ($this->ModelName->id = $id)。在使用该方法时,$fieldName 应当只包含字段名,而不是模型名和字段名。

    例如,更新一篇博客文章(blog post)的标题(title),在控制器中调用 saveField方法可以象下面这样:

    1. $this->Post->saveField('title', 'A New Title for a New Day');

    警告

    在使用这个方法时不能阻止更新 modified 字段,你需要使用 save() 方法才行。

    saveField 方法也有另一种语法:

    1. saveField(string $fieldName, string $fieldValue, array $params = array())

    $params 数组可以用如下任意选项作为键:

    • validate 设置为 true/false 来开启/关闭验证。
    • callbacks 设置为 false 来关闭回调。使用 'before' 或 'after' 将仅开启指定的回调。
    • (从 2.4 版本开始)控制计数器缓存(如果有的话)更新的布尔值

    Model::updateAll(array $fields, mixed $conditions)

    用一次调用更新一条或多条记录。要更新的字段和它们的值,由 $fields 数组确定。要更新的记录由 $conditions 数组确定。如果 $conditions 参数未提供或设置为true,全部记录都会被更新。

    例如,批准所有成为会员超过一年的 bakers,更新调用可以象下面这样:

    1. $thisYear = date('Y-m-d H:i:s', strtotime('-1 year'));
    2.  
    3. $this->Baker->updateAll(
    4. array('Baker.approved' => true),
    5. array('Baker.created <=' => $thisYear)
    6. );

    $fields 数组可接受 SQL 表达式。常量(literal)值应当使用DboSource::value() 手动引用。例如,如果一个模型方法调用updateAll(),应该这样:

    1. $db = $this->getDataSource();
    2. $value = $db->value($value, 'string');
    3. $this->updateAll(
    4. array('Baker.status' => $value),
    5. array('Baker.status' => 'old')
    6. );

    注解

    例如,关闭所有属于某一客户的所有请求:

    1. $this->Ticket->updateAll(
    2. array('Ticket.status' => "'closed'"),
    3. array('Ticket.customer_id' => 453)
    4. );

    默认情况下,updateAll() 对支持 join 的数据库会自动连接任何 belongsTo 关联。要阻止这种连接,临时解除绑定(unbind)该关联。

    Model::saveMany(array $data = null, array $options = array())

    此方法用于同时保存同一模型的多行。可以使用如下选项:

    • validate: 设置为 false 将关闭验证,设置为 true 将在保存每条记录前进行验证,设置为 'first' 将在保存任何记录前验证 所有 记录(默认值)
    • atomic: 如果为 true (默认值),将试图用单个事务保存所有记录。如果数据库/表不支持事务,则应当设置为 false。
    • fieldList: 同 Model::save() 方法的 $fieldList 参数
    • deep: (从 2.1 版开始) 如果设置为 true,关联数据也被保存;也可参见saveAssociated 方法。
    • callbacks 设置为 false 将关闭回调。使用 'before' 或 'after' 将仅开启指定的回调。
    • counterCache (从 2.4 版本开始)控制计数器缓存(如果有的话)更新的布尔值
      为保存单个模型的多条记录,$data 必须是数字索引的记录数组,象这样:
    1. $data = array(
    2. array('title' => 'title 1'),
    3. array('title' => 'title 2'),
    4. );

    注解

    注意,我们传递了数字索引、而非通常情况下 $data 包含的 Article 键。在保存同一模型的多条记录时,记录数组应当只使用数字索引,而不是模型的键。

    也可以使用如下格式的数据:

    如果要使用 $options['deep'] = true (从 2.1 版本开始)一起保存关联数据,上面的两个例子将象下面这样:

    1. $data = array(
    2. array('title' => 'title 1', 'Assoc' => array('field' => 'value')),
    3. array('title' => 'title 2'),
    4. );
    5. $data = array(
    6. array(
    7. 'Article' => array('title' => 'title 1'),
    8. 'Assoc' => array('field' => 'value')
    9. ),
    10. array('Article' => array('title' => 'title 2')),
    11. );
    12. $Model->saveMany($data, array('deep' => true));

    切记,如果只想更新记录而不是创建新记录,只需要在数据行中加入主键索引:

    1. $data = array(
    2. array(
    3. // 这会创建一个新行
    4. 'Article' => array('title' => 'New article')),
    5. array(
    6. // 这会更新一个现有的行
    7. 'Article' => array('id' => 2, 'title' => 'title 2')),
    8. );

    Model::saveAssociated(array $data = null, array $options = array())

    此方法用于同时保存多个模型关联。可以使用如下选项:

    • validate: 设置为 false 将关闭验证,设置为 true 将在保存每条记录前进行验证,设置为 'first' 将在保存任何记录前验证 全部 记录(默认值)
    • atomic: 如果为 true (默认值),将试图用单个事务保存所有记录。如果数据库/表不支持事务,则应当设置为 false。
    • fieldList: 同 Model::save() 方法的 $fieldList 参数
    • deep: (从 2.1 版开始) 如果设置为 true,不仅保存直接相关的关联数据,也会保存深度嵌套的关联数据。默认值为 false。
    • counterCache (从 2.4 版本开始)控制计数器缓存(如果有的话)更新的布尔值
      要同时保存一条记录及与其有着 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

    为了同时保存一条记录及与其有 hasMany 关联的相关记录、以及深层关联的 CommentbelongsTo User 数据,data 数组应当象这样:

    1. $data = array(
    2. 'Comment' => array(
    3. array('body' => 'Comment 1', 'user_id' => 1),
    4. array(
    5. 'body' => 'Save a new user as well',
    6. 'User' => array('first' => 'mad', 'last' => 'coder')
    7. ),
    8. ),
    9. );

    并用如下语句保存该数据:

    1. $Article->saveAssociated($data, array('deep' => true));

    警告

    当 atomic 选项设置为 false 时,在检查 saveAssociated 的调用(返回值)时要当心,它返回数组,而不是布尔值。

    传入多个模型的 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 是一个以模型别名为键,以字段数组为值的数组。模型名不同于在要保存的数据中那样,不能嵌套。

    在 2.1 版更改: Model::saveAll() 和类似方法现在支持传入多个模型的 fieldList 选项。

    现在你也可以设置 $options['deep'] = true; 来保存深层关联的数据。

    saveAll 函数只是 saveMany 方法和 saveAssociated 方法的包装。它会检查数据并且决定应当执行哪种类型的保存。如果数据是数字索引数组的格式,就会调用saveMany 方法,否则调用 saveAssociated 方法。

    此方法接受与前面的两个方法相同的选项,基本上只是个向后兼容方法。建议(不要使用该方法,而是)根据情况使用 saveMany 方法或 saveAssociated 方法。

    保存相关模型的数据(hasOne, hasMany, belongsTo)

    在与关联模型一起使用时,重要的是要意识到,保存模型数据应当总是由相应的 CakePHP模型来完成。如果保存一条新的 Post 和它关联的 Comment,就要在保存操作的过程中使用Post 和 Comment 模型。

    如果在系统中关联模型双方的记录都还不存在(例如,想要同时保存新的 User 及相关的Profile 记录),就需要先保存主模型或者父模型。

    为了了解这是如何进行的,想像一下在 UsersController 控制器中有一个动作,处理新User 和相关 Profile 的保存。下面的示例动作假设已经(使用 FormHelper)提交(POSTed)了足够的数据,来创建单个 User 和单个 Profile:

    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. // 如果用户保存了,现在添加这条数据到 data 中并保存 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 模型访问
    15. // Profile 模型:
    16. $this->User->Profile->save($this->request->data);
    17. }
    18. }
    19. }

    规则是,当使用 hasOne、hasMany 和 belongsTo 关联时,重要的是如何设置键。基本思路是从一个模型中获取键,并将其放入另一个模型的外键字段中。有时这可能需要在调用save() 方法之后使用模型类的 $id 属性,不过其它情况下可能只需要从刚提交(POSTed)给控制器动作的表单中的隐藏输入项(hidden input)获得 ID。

    为了补充上面使用的基本方法,CakePHP 还提供了一个非常方便的方法saveAssociated(),这让你可以一次验证和保存多个模型的数据。而且,saveAssociated() 方法还提供了事务支持以确保数据库中的数据完整性(例如,如果一个模型保存失败,其它模型也不会保存)。

    注解

    要在 MySQL 中使事务正常工作,表必须使用 InnoDB 引擎。记住,MyISAM 表不支持事务。

    来看看如何使用 方法同时保存 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. }

    保存通过(连接模型)的 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(
    7. 'courseMembershipsList',
    8. $this->CourseMembership->find('all')
    9. );
    10. }
    11.  
    12. public function add() {
    13. if ($this->request->is('post')) {
    14. if ($this->CourseMembership->saveAssociated($this->request->data)) {
    15. return $this->redirect(array('action' => 'index'));
    16. }
    17. }
    18. }
    19. }
    20.  
    21. // View/CourseMemberships/add.ctp
    22.  
    23. <?php echo $this->Form->create('CourseMembership'); ?>
    24. <?php echo $this->Form->input('Student.first_name'); ?>
    25. <?php echo $this->Form->input('Student.last_name'); ?>
    26. <?php echo $this->Form->input('Course.name'); ?>
    27. <?php echo $this->Form->input('CourseMembership.days_attended'); ?>
    28. <?php echo $this->Form->input('CourseMembership.grade'); ?>
    29. <button type="submit">Save</button>
    30. <?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. )

    在 CakePHP 中,使用这种数据结构调用 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 的两个字段,例如:

    所得到的 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. )

    利用 saveAssociated 方法,CakePHP 仍然可以很容易地把 Student id 和 Course id放入 CourseMembership 中。

    保存通过 hasOne、belongsTo 和 hasMany 关联的模型非常简单:只需要将关联模型的 ID填入外键字段。 一旦完成,只要调用模型的 save() 方法,所有数据就被正确地连接起来了。下面的示例是传递给 Tag 模型的 save() 方法的数据数组的格式:

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

    也可以使用这种格式调用 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] => 43
    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)到文章(Post)中。这需要用以下的HABTM 数组格式传入关联的 HABTM 数据。注意,只需要传入关联的 HABTM 模型的 id,不论需要再怎样嵌套:

    1. Array
    2. (
    3. [0] => Array
    4. (
    5. [Post] => Array
    6. (
    7. [title] => 'Saving HABTM arrays'
    8. )
    9. [Tag] => Array
    10. (
    11. [Tag] => Array(1, 2, 5, 9)
    12. )
    13. )
    14. [1] => Array
    15. (
    16. [Post] => Array
    17. (
    18. [title] => 'Dr Who\'s Name is Revealed'
    19. )
    20. [Tag] => Array
    21. (
    22. [Tag] => Array(7, 9, 15, 19)
    23. )
    24. )
    25. [2] => Array
    26. (
    27. [Post] => Array
    28. (
    29. [title] => 'I Came, I Saw and I Conquered'
    30. )
    31. [Tag] => Array
    32. (
    33. [Tag] => Array(11, 12, 15, 19)
    34. )
    35. )
    36. [3] => Array
    37. (
    38. [Post] => Array
    39. (
    40. [title] => 'Simplicity is the Ultimate Sophistication'
    41. )
    42. [Tag] => Array
    43. (
    44. [Tag] => Array(12, 22, 25, 29)
    45. )
    46. )
    47. )

    把上面的数组传入 saveAll($data, array('deep' => true)),会在 posts_tags 连接表中填入 Tag 和 Post 之间的关联。

    作为示例,我们来创建一个表单,用来创建新的标签(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 隐藏字段的值被设置为 tag 要连接的 recipe的 ID。

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

    1. public function add() {
    2. // Save the association
    3. if ($this->Tag->save($this->request->data)) {
    4. // do something on success
    5. }
    6. }

    调用上面这段代码,将创建新的 Tag 并与 Recipe 相关联,其 ID 为$this->request->data['Recipe']['id']

    其它我们可能希望呈现关联数据的方式,可以包括下拉列表。数据可以使用find('list') 方法从模型中取出,并且赋给用模型名命名的视图变量。同名的输入项(input)会自动把该数据放入 <select> 元素中:

    1. // 在控制器中:
    2. $this->set('tags', $this->Recipe->Tag->find('list'));
    3.  
    4. // 在视图中:
    5. $this->Form->input('tags');

    HABTM 关系更可能的情形会包含一个允许多选的 <select> 元素。例如,一个菜单(Recipe)可以被贴上多个标签(Tag)。在这种情况下,数据以相同的方式从模型中取出,但是表单的输入项(input)定义稍有不同。tag 名称使用 ModelName 约定来定义:

    1. // 在控制器中:
    2. $this->set('tags', $this->Recipe->Tag->find('list'));
    3.  
    4. // 在视图中:
    5. $this->Form->input('Tag');

    使用上面这段代码,会创建多选的下拉列表(drop down),允许多个选项自动被保存到数据库中已添加或已保存的现有 Recipe 上。

    自我 HABTM

    通常 HABTM 关联用于绑定2个模型,但是它也可以用于1个模型,不过这需要更加小心。

    关键在于模型的设置 className。简单地添加 Project HABTM Project 关联会引起保存数据时的错误。设置 className 为模型名称,并用别名作为键,就避免了这些问题。

    1. class Project extends AppModel {
    2. public $hasAndBelongsToMany = array(
    3. 'RelatedProject' => array(
    4. 'className' => 'Project',
    5. 'foreignKey' => 'projects_a_id',
    6. 'associationForeignKey' => 'projects_b_id',
    7. ),
    8. );
    9. }

    创建表单元素,保存数据,都象以前一样,但是要使用别名。这样的代码:

    1. $this->set('projects', $this->Project->find('list'));
    2. $this->Form->input('Project');

    就变成这样:

    1. $this->set('relatedProjects', $this->Project->find('list'));
    2. $this->Form->input('RelatedProject');

    当 HABTM 变得复杂时怎么办?

    默认情况下,当保存 HasAndBelongsToMany 关系时,在保存新行之前 CakePHP 会先删除连接表中的所有(相关)行。 例如,一个 Club 有10个相关的 Children,然后更新 Club 为只有2个 children。这样,Club 将只有2个 Children,而不是12个。

    也要注意,如果想要在连接中加入更多字段(何时创建或者其它数据),这在使用 HABTM 连接表时是可能的,不过重要的是要明白你有简单的解决办法。

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

    考虑这个例子:

    1. Child hasAndBelongsToMany Club

    另一种看待它的方法是添加一个 Membership 模型:

    1. Child hasMany Membership
    2. Membership belongsTo Child, Club
    3. Club hasMany Membership.

    这两个例子几乎是完全相同的。它们在数据库中使用了相同数量的命名字段,相同数量的模型。重要的区别是,"连接(join)" 模型命名不同,并且其行为更容易预知。

    小技巧

    当连接表包含两个外键以外的额外字段时,通过将数组的键 'unique' 设置为'keepExisting',能够防止丢失额外字段的值。你可以认为这与设置'unique' => true 类似,但在保存操作过程中不会丢失额外字段的数据。另外,如果你使用 bake 来创建模型,自动会设置成这样。参见HABTM 关联数组

    不过,在大多数情况下,象上面的例子那样为连接表建立模型,设置 hasMany、belongsTo关联,比使用 HABTM 关联更简单。

    数据库表

    虽然 CakePHP 可以有非数据库驱动的数据源,但大多数时候是数据库驱动的。CakePHP 被设计成与(数据库)无关,可以使用 MySQL、Microsoft SQL Server、PostgreSQL 和其它数据库。你可以象平时那样创建数据库表。在创建模型类时,模型将自动映射到你创建的表上。按照约定,表名为小写、复数,多个单词的表名用下划线分隔。例如,名为 Ingredient 的模型对应的表名为 ingredients。名为 EventRegistration 的模型对应的表名为 event_registrations。CakePHP 会检视表来决定每个字段的数据类型,并使用这些信息自动化各种特性,比如输出视图中的表单字段。按照约定,字段名为小写并用下划线分隔。

    使用 created 和 modified 列

    如果在数据库表中定义 created 和/或 modified 字段为 datetime 字段(缺省值null),CakePHP 能够识别这些字段,每当创建或保存一条记录到数据库时,自动填入这两个字段(除非要保存的数据中已经包含了这两个字段的值)。

    在最初添加记录时,createdmodified 字段会被设置为当前日期和时间。每当保存现有记录时,modified 字段会被更新为当前日期和时间。

    如果在调用 Model::save() 之前 ,$this->data 中包含了 createdmodified字段的数据(例如来自 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. // 在每次调用 save 方法前清除 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. }