发新话题
打印

CakePHP中文手册- - the rapid development php framework

保存数据

当需要保存model对象时(译注:或者使用持久化这个词更贴切),你需要提供如下形式的数据给sava()方法:

Array  
(  
    [ModelName] => Array  
        (  
            [fieldname1] => 'value'  
            [fieldname2] => 'value'  
        )  
)  
      
Array
(
    [ModelName] => Array
        (
            [fieldname1] => 'value'
            [fieldname2] => 'value'
        )
)
         
为了能够让数据通过这样的形式提交给controller,最方便的办法就是使用HTML Helper来完成这个任务,因为HTML Helper会为提交的Form封装成Cake希望的这种形式。当然你可以不使用,只要页面上的元素的name被设置成date[Modelname][fieldname]形式就ok了。不过我还是要说,$html->input('Model/fieldname')是最方便的办法。
(译注:OGNL的php版,太棒了,我的意见是如果不嫌麻烦的话尽量使用类似OGNL的做法,因为tag产生的页面在设计期是无法预览的,我相信web应用前台页面的设计的复杂性并不亚于后台业务逻辑)

从页面Form中提交的数据自动被格式化并且注入到controller中的$this->data变量,所以保存从web页面提交的数据只是举手之劳。下面我们来看一段示例代码:

function edit($id)  
{  
  
   //Note: The property model is automatically loaded for us at $this->Property.  
  
   // Check to see if we have form data...  
   if (emptyempty($this->data))  
   {  
        $this->Property->id = $id;  
        $this->data = $this->Property->read();//populate the form fields with the current row  
   }  
   else  
   {  
      // Here's where we try to save our data. Automagic validation checking  
      if ($this->Property->save($this->data['Property']))
      {
         //Flash a message and redirect.
         $this->flash('Your information has been saved.',
                     '/properties/view/'.$this->data['Property']['id'], 2);  
      }  
      //if some fields are invalid or save fails the form will render  
   }  
}  
      
function edit($id)
{

   //Note: The property model is automatically loaded for us at $this->Property.

   // Check to see if we have form data...
   if (empty($this->data))
   {
        $this->Property->id = $id;
        $this->data = $this->Property->read();//populate the form fields with the current row
   }
   else
   {
      // Here's where we try to save our data. Automagic validation checking
      if ($this->Property->save($this->data['Property']))
      {
         //Flash a message and redirect.
         $this->flash('Your information has been saved.',
                     '/properties/view/'.$this->data['Property']['id'], 2);
      }
      //if some fields are invalid or save fails the form will render
   }
}
         
注意保存数据前会触发model的校验机制,更多关于数据校验的内容请参见Chapter 12。如果你不希望进行数据校验,可以使用save($date, false)。

TOP

其他一些有用的数据保存函数

del
  string $id
  boolean $cascade
       
删除指定id的model对象,或者当前model对象。
如果$cascade设为true,则会级联删除当前model所关联的所有model中,设为'dependent'的model对象。删除成功返回true

saveField
  string $name
  string $value
       
保存单个属性值。

getLastInsertId

返回最后当前ID属性的nextValue。
[ToDo 扩展支持多种主键生成策略 例如sequence UUID]
       
Model中的回调函数
我们在model中增加了一些回调函数以帮助你在model操作前后能够织入一些业务逻辑(原文为sneak in,借用了AOP中的织入一词,因为从操作来看这些回调函数等同于AOP中的advice)。为了获得这样的能力,需要使用model中的一些参数并且在你的model中覆写这些方法。

beforeFind
  string $conditions
       
beforeFind()回调函数是在model的find方法执行前的前置操作。你可以加入任何检索前的业务逻辑。你覆写该方法只要保证在前置操作成功后返回true来执行真正的find方法,返回false中断find方法就可以了。(译注:在一些复杂场景中,需多次持久化的情况下请慎用)

afterFind
  array $results
       
使用afterFind回调函数能够更改find方法的返回结果集,或者在检索动作完成后加上一些业务逻辑。该函数的参数$results为经过回调函数处理以后的find检索结果集。

beforeValidate
       
beforeValidate回调函数能够在model校验数据之前更改model中的一些数据。同样也可以用来在model校验之前加入更为复杂的额外校验规则。和beforeFind一样,必须保证返回true来调用真正的操作,返回false来中断校验乃至save操作。

beforeSave
       
和先前介绍的回调函数类似,在校验完成之后,保存动作之前加入额外的处理(如果校验失败是不会触发该回调函数的)。返回true或者false,不再赘述。
一个比较常见的beforeSave的应用场景就是在保存动作之前格式化日期属性以适应不同的数据库:

// Date/time fields created by HTML Helper:  
// This code would be seen in a view  
  
$html->dayOptionTag('Event/start');  
$html->monthOptionTag('Event/start');  
$html->yearOptionTag('Event/start');  
$html->hourOptionTag('Event/start');  
$html->minuteOptionTag('Event/start');  
  
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/  
  
// Model callback functions used to stitch date  
// data together for storage  
// This code would be seen in the Event model:  
  
function beforeSave()  
{  
    $this->data['Event']['start'] = $this->_getDate('Event', 'start');  
  
    return true;  
}  
  
function _getDate($model, $field)  
{  
    return date('Y-m-d H:i:s', mktime(  
        intval($this->data[$model][$field . '_hour']),  
        intval($this->data[$model][$field . '_min']),  
        null,  
        intval($this->data[$model][$field . '_month']),  
        intval($this->data[$model][$field . '_day']),  
        intval($this->data[$model][$field . '_year'])));  
}  
  
      
// Date/time fields created by HTML Helper:
// This code would be seen in a view

$html->dayOptionTag('Event/start');
$html->monthOptionTag('Event/start');
$html->yearOptionTag('Event/start');
$html->hourOptionTag('Event/start');
$html->minuteOptionTag('Event/start');

/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

// Model callback functions used to stitch date
// data together for storage
// This code would be seen in the Event model:

function beforeSave()
{
    $this->data['Event']['start'] = $this->_getDate('Event', 'start');

    return true;
}

function _getDate($model, $field)
{
    return date('Y-m-d H:i:s', mktime(
        intval($this->data[$model][$field . '_hour']),
        intval($this->data[$model][$field . '_min']),
        null,
        intval($this->data[$model][$field . '_month']),
        intval($this->data[$model][$field . '_day']),
        intval($this->data[$model][$field . '_year'])));
}

         afterSave
       
保存完成后执行的动作。[ToDo 如果保存出错是否会触发?]

beforeDelete

afterDelete
       
不需要多说了吧,删除操作前后的回调函数。

TOP

Section 3 Model中的变量
当你创建一个新的model的时候,有很多特殊的变量可以设置,从而是model具备Cake赋予的相应操作。

$primaryKey
如果主键字段不为'id',COC无法发挥的时候,你可以通过该变量来指定主键字段名字。

$recursive
这个我们上面已经介绍过了,指定了model级联关联对象的深度。

想象这样一个场景,Group下的User下面还有各自的Articles。

$recursive = 0 Cake只会检索Group对象
$recursive = 1 Cake会检索包含User对象的Group对象
$recursive = 2 Cake会检索完成的包含Article的Group对象
       
$transactional
决定Model是否允许事务处理,true 或者 false。注意,仅在支持事务的数据库上有效。

$useTable
和$primaryKey一样,如果你的表名不能和model匹配的话,而且你也不想或者不能修改表名,则可以通过该变量来指定数据表名。

$validate
该变量为一个array,表示一组校验规则,详细请参见Chapter 12。

$useDbConfig
还记得我们在配制一章介绍的如何配置数据库连接变量吗?可以通过该变量非常方便的切换数据库连接,默认的是什么?猜一下,当然就是'default',rails就是让你忘却那些难以记忆的配置。

TOP

Section 4 [重头戏]关联对象
简介
CakePHP中提供的一个非常重要的功能就是关联数据表间的映射。在CakePHP中,关联表通过association(关联)来处理。关联是这些逻辑上相关的单元间的胶水一般。

CakePHP中一共有4种类型的关联:

hasOne

hasMany

belongsTo

hasAndBelongsToMany
       
一旦model间的关联关系被定义,Cake能够自动检索你当前操作的model中包含的关联对象。也就是将基于关系数据库的数据映射为基于对象的领域模型。举个例子,Post和Author之间是hasMany(一对多)关系,当我们在controller中通过$this->Post->findAll()来检索所有的Post对象时,也会同时把所有关联的Author对象检索出来。

遵循CakePHP的命名约定是正确定义关联关系的有利保证(参见附录 B)。如果你使用了CakePHP的命名约定,可以通过脚手架来可视化你的数据,因为脚手架会自动侦测并使用你定义的关联关系,这样也能某种程度上供你检查是否定义正确。当然,你也可以完全不使用命名约定来定义关联,不过我们稍候再介绍关于这种定义。现在我们仅关注使用命名约定的情况下的关联定义。命名约定中我们需要考虑的有这3个内容,外键,model名字,表名。

这里先简单的介绍一下关于这3者的要求,更为详细的请查看附录:

外键:[单数形式的model名字]_id 假设在"authors"表中有Post的外键关联,则外键字段名字应该为 "post_id"。
表名:[复数形式的model名字] 表名必须为model名字的复数形式,例如:"posts" "authors"。
Model的名字:[驼峰法命名 单数形式]。

CakePHP的脚手架希望你的关联定义的顺序和表中的外键字段的顺序是一致的。所以如果我有一个Article对象[belongsTo(属于)]另外3个对象(Author Editor Publisher)的话,我需要3个外键author_id, editor_id, publisher_id。脚手架要求你model中对应的关联和数据库中的列顺序保持一致。

为了更好的描述关联对象是如何运作的,让我们继续以Blog为应用场景来介绍。假设我们现在需要为Blog系统创建一个简单的用户管理模块。我们假设我们不需要跟踪用户情况,但是我们希望每个用户都一个个人记录(用户 [hasOne] 个人记录)。用户可以创建多条评论记录(用户 [hasMany] 评论记录)。同样的,我们的文章会被分配到多个tag,同时每个tag都包含多篇文章,也就是多对多关系 (文章 [hasAndBelongsToMany] Tag)。

TOP

hasOne 关联的定义与查询
假设你已经准备好了User和Profile两个model,让我们来定义他们之间的关联。hasOne关联的定义是通过在model中增加一个array来实现的。下面是示例代码:

/app/models/user.php hasOne  
<?php  
class User extends AppModel  
{  
    var $name = 'User';  
    var $hasOne = array('Profile' =>  
                        array('className'    => 'Profile',  
                              'conditions'   => '',  
                              'order'        => '',  
                              'dependent'    =>  true,  
                              'foreignKey'   => 'user_id'  
                        )  
                  );  
}  
?>  
      
/app/models/user.php hasOne
<?php
class User extends AppModel
{
    var $name = 'User';
    var $hasOne = array('Profile' =>
                        array('className'    => 'Profile',
                              'conditions'   => '',
                              'order'        => '',
                              'dependent'    =>  true,
                              'foreignKey'   => 'user_id'
                        )
                  );
}
?>
         
$hasOne变量是一个array,Cake通过该变量来构建User与Profile之间的关联。我们来看每一个元素代表的意义:

className (required):关联对象的类名,上面代码中我们设为'Profile'表示关联的是Profile对象。
conditions: 关联对象的选择条件,(译注:类似hibernate中的formula)。具体到我们的例子来看,假设我们仅关联Profile的header color为绿色的文件记录,我们可以这样定义conditions,"Profile.header_color = 'green'"。
order: 关联对象的排序方式。假设你希望关联的对象是经过排序的,你可以为order赋值,就如同SQL中的order by子句:"Profile.name ASC"。
dependent:这是个布尔值,如果为true,父对象删除时会级联删除关联子对象。在我们的Blog中,如果"Bob"这个用户被删除了,则关联的Profile都会被删除。类似一个外键约束。
foreignKey:指向关联Model的外键字段名。仅在你不遵循Cake的命名约定时需要设置。
现在,现在当我们使用find() findAll()检索User对象时,你会发现关联的Profile对象也被检索回来,非常的方便:

$user = $this->User->read(null, '25');  
print_r($user);  
  
//output:  
  
Array  
(  
    [User] => Array  
        (  
            [id] => 25  
            [first_name] => John  
            [last_name] => Anderson  
            [username] => psychic  
            [password] => c4k3roxx  
        )  
  
    [Profile] => Array  
        (  
            [id] => 4  
            [name] => Cool Blue  
            [header_color] => aquamarine  
            [user_id] = 25  
        )  
)

TOP

belongsTo关联的定义与使用
现在User对象能够得到对应的Profile对象,当然我们也应该为Profile对象定义一个关联使之能够获取它的所有者,也就是对应的User对象。在Cake中,我们使用belongsTo关联来实现:

/app/models/profile.php belongsTo  
<?php  
class Profile extends AppModel  
{  
    var $name = 'Profile';  
    var $belongsTo = array('User' =>  
                           array('className'  => 'User',  
                                 'conditions' => '',  
                                 'order'      => '',  
                                 'foreignKey' => 'user_id'  
                           )  
                     );  
}  
?>  
      
/app/models/profile.php belongsTo
<?php
class Profile extends AppModel
{
    var $name = 'Profile';
    var $belongsTo = array('User' =>
                           array('className'  => 'User',
                                 'conditions' => '',
                                 'order'      => '',
                                 'foreignKey' => 'user_id'
                           )
                     );
}
?>
         
和hasOne关联一样,belongsTo也是一个array变量,你可以通过设置其中的值来具体定义belongsTo关联:

className (required): 关联对象的类名,这里我们关联的是User对象,所以应该是'User'。
conditions: SQL条件子句以限定关联的对象,假定我们只允许Profile关联状态为active的用户,我们可以这样写:"User.active = '1'",当然也可以是类似的其它条件。
order:关联对象的排序子句,假如你希望关联的对象经过排序,你可以类似"User.last_name ASC"这样来定义。
foreignKey:关联对象所对应的外键字段名,仅在你不遵循Cake的命名约定时需要设置。
现在当我们使用find() findAll()来检索Profile对象时,会发现关联的User对象也一同被检索回来。

$profile = $this->Profile->read(null, '4');  
print_r($profile);  
  
//output:  
  
Array  
(  
  
    [Profile] => Array  
        (  
            [id] => 4  
            [name] => Cool Blue  
            [header_color] => aquamarine  
            [user_id] = 25  
        )  
  
    [User] => Array  
        (  
            [id] => 25  
            [first_name] => John  
            [last_name] => Anderson  
            [username] => psychic  
            [password] => c4k3roxx  
        )  
)

TOP

hasMany关联的定义与查询
我们已经为User和Profile对象建立起了双向关联,那让我们开始为User和Comment对象之间建立关联吧,先看下面的示例代码:

/app/models/user.php hasMany  
<?php  
class User extends AppModel  
{  
    var $name = 'User';  
    var $hasMany = array('Comment' =>  
                         array('className'     => 'Comment',  
                               'conditions'    => 'Comment.moderated = 1',  
                               'order'         => 'Comment.created DESC',  
                               'limit'         => '5',  
                               'foreignKey'    => 'user_id',  
                               'dependent'     => true,  
                               'exclusive'     => false,  
                               'finderQuery'   => ''  
                         )  
                  );  
  
    // Here's the hasOne relationship we defined earlier...  
    var $hasOne = array('Profile' =>
                        array('className'    => 'Profile',
                              'conditions'   => '',
                              'order'        => '',
                              'dependent'    =>  true,
                              'foreignKey'   => 'user_id'  
                        )  
                  );  
}  
?>  
      
/app/models/user.php hasMany
<?php
class User extends AppModel
{
    var $name = 'User';
    var $hasMany = array('Comment' =>
                         array('className'     => 'Comment',
                               'conditions'    => 'Comment.moderated = 1',
                               'order'         => 'Comment.created DESC',
                               'limit'         => '5',
                               'foreignKey'    => 'user_id',
                               'dependent'     => true,
                               'exclusive'     => false,
                               'finderQuery'   => ''
                         )
                  );

    // Here's the hasOne relationship we defined earlier...
    var $hasOne = array('Profile' =>
                        array('className'    => 'Profile',
                              'conditions'   => '',
                              'order'        => '',
                              'dependent'    =>  true,
                              'foreignKey'   => 'user_id'
                        )
                  );
}
?>
         
$hasMany array用来定义User包含多条Comment这样的关联关系。还是老样子,介绍一下包含的key,但是一些和之前同样含义的key我将不再赘述详细。

className (required):关联对象类名。
conditions: 关联对象限定条件。
order: 关联对象排序子句。
limit:因为是一对多关系,所以可以通过limit来限定检索的关联对象数量。比如我们可以只关联5条评论记录。
foreignKey:外键字段名。仅当不遵循命名约定时起用。
dependent:是否级联删除。(该动作可能会造成数据的误删除,请谨慎设定)
exclusive:如果设为true,所有的关联对象将在一句sql中删除,model的beforeDelete回调函数不会被执行。但是如果没有复杂的逻辑在级联删除中,这样的设定会带来性能上的优势。(译注:Cake的确方便,但是使用时一定要记住控制sql语句发送数量)
finderQuery:定义一句完整的sql语句来检索关联对象,能够对关联规则进行最大程度上的控制。当关联关系特别复杂的时候,比如one table - many model one model - many table的情况下,Cake无法准确的替你完成映射动作,需要你自己来完成这个艰巨的任务。
现在看一下如何在检索user对象的时候一并读回comment对象集合

$user = $this->User->read(null, '25');  
print_r($user);  
  
//output:  
  
Array  
(  
    [User] => Array  
        (  
            [id] => 25  
            [first_name] => John  
            [last_name] => Anderson  
            [username] => psychic  
            [password] => c4k3roxx  
        )  
  
    [Profile] => Array  
        (  
            [id] => 4  
            [name] => Cool Blue  
            [header_color] => aquamarine  
            [user_id] = 25  
        )  
  
    [Comment] => Array  
        (  
            [0] => Array  
                (  
                    [id] => 247  
                    [user_id] => 25  
                    [body] => The hasMany assocation is nice to have.  
                )  
  
            [1] => Array  
                (  
                    [id] => 256  
                    [user_id] => 25  
                    [body] => The hasMany assocation is really nice to have.  
                )  
  
            [2] => Array  
                (  
                    [id] => 269  
                    [user_id] => 25  
                    [body] => The hasMany assocation is really, really nice to have.  
                )  
  
            [3] => Array  
                (  
                    [id] => 285  
                    [user_id] => 25  
                    [body] => The hasMany assocation is extremely nice to have.  
                )  
  
            [4] => Array  
                (  
                    [id] => 286  
                    [user_id] => 25  
                    [body] => The hasMany assocation is super nice to have.  
                )  
  
        )  
)  
      
$user = $this->User->read(null, '25');
print_r($user);

//output:

Array
(
    [User] => Array
        (
            [id] => 25
            [first_name] => John
            [last_name] => Anderson
            [username] => psychic
            [password] => c4k3roxx
        )

    [Profile] => Array
        (
            [id] => 4
            [name] => Cool Blue
            [header_color] => aquamarine
            [user_id] = 25
        )

    [Comment] => Array
        (
            [0] => Array
                (
                    [id] => 247
                    [user_id] => 25
                    [body] => The hasMany assocation is nice to have.
                )

            [1] => Array
                (
                    [id] => 256
                    [user_id] => 25
                    [body] => The hasMany assocation is really nice to have.
                )

            [2] => Array
                (
                    [id] => 269
                    [user_id] => 25
                    [body] => The hasMany assocation is really, really nice to have.
                )

            [3] => Array
                (
                    [id] => 285
                    [user_id] => 25
                    [body] => The hasMany assocation is extremely nice to have.
                )

            [4] => Array
                (
                    [id] => 286
                    [user_id] => 25
                    [body] => The hasMany assocation is super nice to have.
                )

        )
)
         
你同样可以为Comment加上关联User对象的belongsTo关联,但是在文档中就不再详细描述了。

TOP

hasAndBelongsToMany关联的定义与查询
我相信你已经掌握了简单的关联定义,让我们来看最后一个,也是最为复杂的关联关系:hasAndBelongsToMany(HABTM)。这个关联会让你头大的,不过也是最有用的。(译注:我倒认为应该数据库设计上尽量的避免出现大量的多对多关联,有的时候多对多关联可以比较简单拆分为两个一对多关联。)HABTM关联也就是3张表的关联关系,关系数据库中应该说只存在多对一外键关联,所以如果要做多对多关联必然需要一张关联表来保存关联关系。

hasMany和hasAndBelongsToMany的不同处在于,hasMany关联所关联的对象只会属于本对象,不会同时属于其他对象。但是HABTM不同,所关联的对象同时会被其他对象所关联持有。比如Post和Tag之间的关联就是这种关系,一篇日志可以属于多个不同的Tag,一个Tag也会包含多篇不同的日志。

为了实现多对多关联,首先要建立那张关联关系表(参照表)。除了"tags" "posts"表以外,根据Cake的命名约定,关联表的名字应该是[复数形式的model1名字]_[复数形式的model2名字],至于两个model谁先谁后则根据字典排序法。

下面是一些示例:
Posts and Tags: posts_tags
Monkeys and IceCubes: ice_cubes_monkeys
Categories and Articles: articles_categories

关联表至少需要两个关联对象的外键字段,例如"post_id" 和 "tag_id"。当然你也可以加入一些其他的属性。

下面是生成的数据库脚本:

Here's what the SQL dumps will look like for our Posts HABTM Tags example:

--
-- Table structure for table `posts`
--

CREATE TABLE `posts` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `user_id` int(10) default NULL,
  `title` varchar(50) default NULL,
  `body` text,
  `created` datetime default NULL,
  `modified` datetime default NULL,
  `status` tinyint(1) NOT NULL default '0',
  PRIMARY KEY  (`id`)
) TYPE=MyISAM;

-- --------------------------------------------------------

--
-- Table structure for table `posts_tags`
--

CREATE TABLE `posts_tags` (
  `post_id` int(10) unsigned NOT NULL default '0',
  `tag_id` int(10) unsigned NOT NULL default '0',
  PRIMARY KEY  (`post_id`,`tag_id`)
) TYPE=MyISAM;

-- --------------------------------------------------------

--
-- Table structure for table `tags`
--

CREATE TABLE `tags` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `tag` varchar(100) default NULL,
  PRIMARY KEY  (`id`)
) TYPE=MyISAM;With our tables set up, let's define the association in the Post model:

/app/models/post.php hasAndBelongsToMany
<?php
class Post extends AppModel
{
    var $name = 'Post';
    var $hasAndBelongsToMany = array('Tag' =>
                               array('className'    => 'Tag',
                                     'joinTable'    => 'posts_tags',
                                     'foreignKey'   => 'post_id',
                                     'associationForeignKey'=> 'tag_id',
                                     'conditions'   => '',
                                     'order'        => '',
                                     'limit'        => '',
                                     'uniq'         => true,
                                     'finderQuery'  => '',
                                     'deleteQuery'  => '',
                               )
                               );
}
?>
       
$hasAndBelongsToMany array是定义HABTM关联的变量,简单介绍一下需要定义的key:

className (required):关联对象类名。
joinTable:如果你没有遵循Cake的命名约定建立关联表的话,则需要设置该key来指定关联表。
foreignKey:注意和associationForeignKey的区别,这个是定义本model在关联表中的外键字段。当然也是仅在你没有遵循Cake命名约定的时候才需要。
associationForeignKey:关联表中指向关联对象的外键字段名。
conditions:关联对象限定条件。
order: 关联对象排序子句。
limit:关联对象检索数量限制。
uniq:设为true的话,重复的关联对象将被过滤掉。
finderQuery:完整的关联对象检索语句。
deleteQuery:完整的删除关联关系的sql语句。当你需要自己实现删除操作的时候可以使用该值。
最后我们来看一下代码:  
  
$post = $this->Post->read(null, '2');  
print_r($post);  
  
//output:  
  
Array  
(  
    [Post] => Array  
        (  
            [id] => 2  
            [user_id] => 25  
            [title] => Cake Model Associations  
            [body] => Time saving, easy, and powerful.  
            [created] => 2006-04-15 09:33:24  
            [modified] => 2006-04-15 09:33:24  
            [status] => 1  
        )  
  
    [Tag] => Array  
        (  
            [0] => Array  
                (  
                    [id] => 247  
                    [tag] => CakePHP  
                )  
  
            [1] => Array  
                (  
                    [id] => 256  
                    [tag] => Powerful Software  
                )  
        )  
)

TOP

保存关联对象
请记住一件非常重要的事情,当保存对象时,很多时候需要同时保存关联对象,比如当我们保存Post对象和它关联的Comment对象时,我们会同时用到Post和Comment两个model的操作。

抽象出来说,当关联的两个对象都没有持久化(即未保存在数据库中),你需要首先持久化主对象,或者是父对象。我们通过保存Post和关联的一条Comment这个场景来具体看看是如何操作的:

//------------Post Comment都没有持久化------------  
  
/app/controllers/posts_controller.php (partial)  
function add()  
{  
    if (!emptyempty($this->data))  
    {  
        //We can save the Post data:   
        //it should be in $this->data['Post']  
         
        $this->Post->save($this->data);  
  
        //Now, we'll need to save the Comment data  
        //But first, we need to know the ID for the   
        //Post we just saved...  
  
        $post_id = $this->Post->getLastInsertId();  
  
        //Now we add this information to the save data  
        //and save the comment.  
  
        $this->data['Comment']['post_id'] = $post_id;  
  
        //Because our Post hasMany Comments, we can access  
        //the Comment model through the Post model:  
  
        $this->Post->Comment->save($this->data);  
  
    }  
}  
      
//------------Post Comment都没有持久化------------

/app/controllers/posts_controller.php (partial)
function add()
{
    if (!empty($this->data))
    {
        //We can save the Post data:
        //it should be in $this->data['Post']
        
        $this->Post->save($this->data);

        //Now, we'll need to save the Comment data
        //But first, we need to know the ID for the
        //Post we just saved...

        $post_id = $this->Post->getLastInsertId();

        //Now we add this information to the save data
        //and save the comment.

        $this->data['Comment']['post_id'] = $post_id;

        //Because our Post hasMany Comments, we can access
        //the Comment model through the Post model:

        $this->Post->Comment->save($this->data);

    }
}
         
换一种情形,假设为现有的一篇Post添加一个新的Comment记录,你需要知道父对象的ID。你可以通过URL来传递这个参数或者使用一个Hidden字段来提交。

/app/controllers/posts_controller.php (partial)  
//Here's how it would look if the URL param is used...  
function addComment($post_id)  
{  
    if (!emptyempty($this->data))  
    {  
        //You might want to make the $post_id data more safe,  
        //but this will suffice for a working example..  
  
        $this->data['Comment']['post_id'] = $post_id;  
  
        //Because our Post hasMany Comments, we can access  
        //the Comment model through the Post model:  
  
        $this->Post->Comment->save($this->data);  
    }  
}  
      
/app/controllers/posts_controller.php (partial)
//Here's how it would look if the URL param is used...
function addComment($post_id)
{
    if (!empty($this->data))
    {
        //You might want to make the $post_id data more safe,
        //but this will suffice for a working example..

        $this->data['Comment']['post_id'] = $post_id;

        //Because our Post hasMany Comments, we can access
        //the Comment model through the Post model:

        $this->Post->Comment->save($this->data);
    }
}
         
如果你使用hidden字段来提交ID这个参数,你需要对这个隐藏元素命名(如果你使用HtmlHelper)来正确提交:

假设日志的ID我们这样来命名$post['Post']['id']

<?php echo $html->hidden('Comment/post_id', array('value' => $post['Post']['id'])); ?>

这样来命名的话,Post对象的ID可以通过$this->data['Comment']['post_id']来访问,同样的通过$this->Post->Comment->save($this->data)也能非常简单的调用。

当保存多个子对象时,采用一样的方法,只需要在一个循环中调用save()方法就可以了(但是要记住使用Model::create()方法来初始化对象)。

小结一下,无论是belongsTo, hasOne还是hasMany关联,在保存关联子对象时候都要记住把父对象的ID保存在子对象中。

TOP

保存 hasAndBelongsToMany 关联对象
如果定义关联一样,最复杂的莫过于 hasAndBelongsToMany 关联,hasOne, belongsTo, hasMany这3种关联只需要很简单的保存一下关联对象外键ID就可以了。但是 hasAndBelongsToMany 却没有那么容易了,不过我们也做了些努力,使之尽可能变得简单些。继续我们的Blog的例子,我们需要保存一个Post,并且关联一些Tag。

实际项目中你需要有一个单独的form来创建新的tag然后来关联它们,不过为了叙述简单,我们假定已经创建完毕了,只介绍如何关联它们的动作。

当我们在Cake中保存一个model,页面上tag的名字(假设你使用了HtmlHelper)应该是这样的格式 'Model/field_name' 。好了,让我们开始看页面代码:

/app/views/posts/add.thtml Form for creating posts

                <h1>Write a New Post</h1>
               
                    <table>   
                    
                    <tr>   
                    
                        <td>Title:</td>  
   
                        <td><?php echo $html->input('Post/title')?></td>
   
                    </tr>
                    
                    <tr>        
                    
                        <td>Body:<td>
   
                        <td><?php echo $html->textarea('Post/title')?></td>
   
                    </tr>
                    
                    <tr>
                    
                    <td colspan="2">
   
                        <?php echo $html->hidden('Post/user_id', array('value'=>$this->controller->Session->read('User.id')))?>
                        
                        <?php echo $html->hidden('Post/status' , array('value'=>'0'))?>
                        
                        <?php echo $html->submit('Save Post')?>
                        
                    </td>
   
                    </tr>
                    
                    </table>
               
上述页面仅仅创建了一个Post记录,我们还需要加入些代码来关联tag:

/app/views/posts/add.thtml (Tag association code added)
<h1>Write a New Post</h1>
    <table>
        <tr>
            <td>Title:</td>
            <td><?php echo $html->input('Post/title')?></td>
        </tr>
        <tr>
            <td>Body:</td>
            <td><?php echo $html->textarea('Post/title')?></td>
        </tr>
        
        <tr>
            <td>Related Tags:</td>
            <td><?php echo $html->selectTag('Tag/Tag', $tags, null, array('multiple' => 'multiple')) ?>
            </td>
        </tr>
        
        <tr>
            <td colspan="2">
                <?php echo $html->hidden('Post/user_id', array('value'=>$this->controller->Session->read('User.id')))?>
                <?php echo $html->hidden('Post/status' , array('value'=>'0'))?>
                <?php echo $html->submit('Save Post')?>
            </td>
        </tr>
</table>
               
我们在controller中通过调用 $this->Post->save() 来保存当前Post以及关联的tag信息,页面元素的命名必须是这样的格式 "Tag/Tag" (Cake Tag Render之后实际的Html Tag名字为 'data[ModelName][ModelName][]' 这样的格式)。提交的数据必须是单个ID,或者是一个ID的array。因为我们使用了一个复选框,所以这里提交的是一个ID的array。

$tags变量是一个array,包含了复选框所需要的tag的ID以及Name信息。

TOP

发新话题