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()方法来初始化对象)。
请记住一件非常重要的事情,当保存对象时,很多时候需要同时保存关联对象,比如当我们保存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信息。
保存 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:
有的时候可能你会需要实时地,动态的改变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
$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]