分类:
2007-09-14 16:11:08
现在你已经掌握了比较简单的关联,让我们转移到最后一个关联:hasAndBelongsToMany (或 HABTM).最后一个是最难的,它会让你团团转,但是它也是其中最有用的一个。如果你有2个model,而这2个model又一个join表联系在一起,HABTM关联这个时候是有用的。Join表抓住了互相关联的各自的行。
hasMany 和hasAndBelongsToMany的区别是:对于hasMany,关联的model是不共享的。这对我们接下来的工作是非常有用:关联Post和Tag model。当一个Tag属于一个Post时,我们不会让他使用完,我们会继续将他和其他Post关联。
为了实现它,我们需要为这个关系建立正确的表。当然你需要为你的Tag model转被一个“tags“表,Post准备一个”posts“表,但是你也需要为此关联创建一个join表。HABTM join表的命名规则是[复数的model1名]_[复数的model2名],这里,model名按字母排序。
HABTM Join 表: model样例以及他们的Join表
1. Posts 和Tags: posts_tags
2. Monkeys和IceCubes: ice_cubes_monkeys
3. Categories和Articles: articles_categories
HABTM Join表至少需要包含2个他们连接的model的外键。在我们的例子中,"post_id" 和"tag_id"就是我们所需要的。
下面导出的SQL看起来是为我们的Posts HABTM Tags实例准备的哟:
-- -- 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; |
随着我们的表已创建,让我们在Post model里定义关联:
/app/models/post.php hasAndBelongsToMany
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数组是Cake用来创建Post和Tag model之间的关联的。数组中的每个键允许你更进一步配置关联:
1. className (必须): 你想要关联的model的类名。例如:
在我们的例子中,我们想指定‘Tag’model 类名。
2. joinTable:它是为没有遵循Cake命名规则的数据库而准备的。如果你的表不是【复数的model1】_【复数的model2】的词语顺序,你可以在这里指定你的表名。
3. foreignKey: 指向当前model的JOIN表的外键名。
在这里,你正在操作一个没有遵循Cake命名规则的数据库。
4. associationForeignKey:指向关联model的外键名。
5. conditions:定义关系的SQL条件语句
我们可以使用它告诉Cake仅关联一个通过验证的Tag。为了实现它,将键值设置为" Tag.approved = 1"或者其他类似的。
6. order:关联model的顺序。
例如,如果你想让你的关联model以某顺序排列,为此键设置一个SQL排序谓语:" Tag.tag DESC "。
7. limit: 你想让Cake获取关联model的最大数
用来限制获取关联的Tag数。
8. uniq:如果设置为true,重复的关联对象会被访问者和查询方法忽略。
一般来说,如果关联不同,将他设置为true。"Awesomeness"的Tag仅可以分配到Post "Cake Model Associations"一次,并且在结果数组中仅出现一次。
9. finderQuery:指定一个完整的SQL语句获取关联。
对于复杂的依赖多个表的关联,这是一种优秀的方法。如果Cake的自动关联不工作,在这里,你可以定义它。
10.deleteQuery:一完整的SQL语句,用来删除HABTM model之间的关联。
如果你不愿意按Cake的方法完成删除操作或者你的创建是以某种方法自己定义的,你可以在这里提供你自己的查询来改变本方法完成操作任务。
现在,当执行Post model的find()或findAll()调用时,在这里我们也应该可以看到我们关联的Tag model:
$post = $this->Post->read(null, '2'); print_r($post); //输出: 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 ) ) ) |
当你操作关联的model时,需要记住的一件重要事情是,保存model数据应该一直由相应的Cake model来完成。如果正在保存一个新的Post以及它的Comment(留言)的话,那么在保存的过程中,你需要使用Post和Comment model。
如果在系统中不存在一个关联的model(例如,你想同时保存一个新的Post和一个相关的Comment),你需要首先保存主要的,或者说是父model。为了知道它如何进行,让我们想象一下,在我们的PostsController中有一个动作(action),它保存新的Post和相关Comment。下面的动作实例假设你已经发布一个Post和Comment。
/app/controllers/posts_controller.php (部分)
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); } } |
尽管如此,如果父Model在系统中已经存在(例如,加一个Comment到一个已存在的Post),在保存之前你需要知道父Model的ID。你可以将此ID作为URL参数,或者作为表单中的一个隐藏元素。
/app/controllers/posts_controller.php (部分)
//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); } } |
如果ID作为一个表单的隐藏元素传递的话,你可能想命名这个字段(如果你正在使用HtmlHelper),以至在发出的数据里它会中止,在这里,它应该为:
如果Post的ID是在 $post['Post']['id']...
echo $html->hidden('Comment/post_id', array('value' => $post['Post']['id'])); ?> |
按这种方式,父Post model的ID可以在$this->data['Comment']['post_id']中访问,并且都为$this->Post->Comment->save($this->data)所准备。
如果你正保存多个子model,这些相同的基本技术都会工作,仅需要将那些save()调用放在一个循环(记住使用Model::Create()清除model信息哟)中。
总之,如果你正保存关联的数据(belongsTo, hasOne, 及 hasMany relations关系),重要的一点是得到父model的ID,并将他保存到子model。