Cakephp 学习日志 (二)

字体大小: 中小 标准 ->行高大小: 标准

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'  
                        )  
                  );  
}  
?>

$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.  
                )  
  
        )  
)  

你同样可以为Comment加上关联User对象的belongsTo关联,但是在文档中就不再详细描述了
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  
                )  
        )  
)  
保存关联对象
请记住一件非常重要的事情,当保存对象时,很多时候需要同时保存关联对象,比如当我们保存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记录,你需要知道父对象的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);  
    }  
}  

如果你使用hidden字段来提交ID这个参数,你需要对这个隐藏元素命名(如果你使用HtmlHelper)来正确提交:

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

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保存在子对象中。
保存 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信息。
使用 bindModel() 和 unbindModel() 实时地改变关联关系
有的时候可能你会需要实时地,动态的改变model的关联关系,比如在一个异常情况下。特别是LazyLoad的问题,有的时候我们并需要一个完整的model,所以我们可以使用 bindModel() 和 unbindModel()来绑定或者解除绑定关联对象。

代码说话,Start:

leader.php and follower.php  
<?php  
  
class Leader extends AppModel  
{  
    var $name = 'Leader';  
  
    var $hasMany = array(  
        'Follower' => array(  
            'className' => 'Follower',  
            'order'     => 'Follower.rank'  
        )  
    );  
}  
  
?>  
  
<?php  
  
class Follower extends AppModel  
{  
    var $name = 'Follower';  
}  
  
?>  

上述两个Model,在Leader Model中,有一个hasMany关联,定义了 "Leader hasMany Followers" 这样的关系。下面我们演示如何在Controller中动态地解除这种关联绑定。

leaders_controller.php (partial)  
function someAction()  
{  
    //This fetches Leaders, and their associated Followers  
    $this->Leader->findAll();  
  
    //Let's remove the hasMany...  
    $this->Leader->unbindModel(array('hasMany' => array('Follower')));
    
    //Now a using a find function will return Leaders, with no Followers
    $this->Leader->findAll();

    //NOTE: unbindModel only affects the very next find function.
    //注意:unbindModel方法只作用一次,第二次find方法调用时则仍然是关联关系有效的
    //An additional find call will use the configured association information.

    //We've already used findAll() after unbindModel(), so this will fetch  
    //Leaders with associated Followers once again...  
    $this->Leader->findAll();  
}  

对于其他各种关联的unbindModel()的用法是类似的,你只需要更改名字和类型就可以了,下面介绍一些基础的Usage:
通用的unbindModel()

$this->Model->unbindModel(array('associationType' => array('associatedModelClassName')));

掌握了如何动态的解除绑定之后,让我们看看如何动态的绑定关联关系。

leaders_controller.php (partial)  
funciton anotherAction()  
{  
    //There is no Leader hasMany Principles in the leader.php model file, so  
    //a find here, only fetches Leaders.  
    $this->Leader->findAll();  
  
    //Let's use bindModel() to add a new association to the Principle model:  
    $this->Leader->bindModel(  
        array('hasMany' => array(
                'Principle' => array(
                    'className' => 'Principle'
                )
            )
        )
    );

    //Now that we're associated correctly, we can use a single find function  
    //to fetch Leaders with their associated principles:  
    $this->Leader->findAll();  
}  

bindModel()方法不单能创建一个关联,同样可以用来动态的修改一个关联。
下面是通常的用法:  

Generic bindModel() example  
$this->Model->bindModel(  
        array('associationName' => array(  
                'associatedModelClassName' => array(  
                    // normal association keys go here...  
                )  
            )  
        )  
    );  

注意:这些的前提是你的数据库表中的外键关联等已经正确设置。
[/quote]

此文章由 http://www.ositren.com 收集整理 ,地址为: http://www.ositren.com/htmls/775.html