热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

CakePHP2.xCookBook中文版第七章模型之关联:将模型连接在一起

使用joins允许你以极为灵活的方式处理CakePHP的关系并获取数据,但是在很多情况下,你能使用其它工具达到同样的目的,例如正确地定义关联,运行时绑定模型或者使用Containable行为。使用这种特性要很小心,因为它在某些情况下可能会带来模式不规范的SQL查询。

关联:将模型连接在一起

CakePHP 的一个非常强劲的特性就是由模型提供关系映射,通过关联来管理多个模型间的连接。

在应用程序的不同对象间定义关系是很自然的。例如:在食谱数据库,一个食谱可能有多个评论,每个评论有一个作者,每个作者可能有多个评论。 以定义这些关系的形式工作,将允许你以一种直观且强大的方式访问你的数据库。

本节的目的是展示如何在 CakePHP 中计划、定义以及利用模型间的关系。

虽然数据可能来自各种源,但在 web 应用程序中最常见的则是存储在关系数据库中。 本节将覆盖这方面的大部分内容。

关于与插件模型一起的关联的信息,请参见 插件模型

关系类型

CakePHP 的关系类型有四种: hasOne、hasMany、belongsTo 和 hasAndBelongsToMany (HABTM)。

关系 关联类型 例子
     
一对多 hasMany 一个用户有多份食谱
多对一 belongsTo 多份食谱属于同一个用户
多对多 hasAndBelongsToMany 多份食谱有且属于多种成分

关联是通过创建一个由你定义的关联命名的类变量来定义的。 此变量有时候可能是简单的字符串,但也可能是用于定义关联细节的复杂的多维数组。

 1 class User extends AppModel {  2 public $hasOne = 'Profile';  3 public $hasMany = array(  4 'Recipe' => array(  5 'className'  => 'Recipe',  6 'conditions' => array('Recipe.approved' => '1'),  7 'order'      => 'Recipe.created DESC'  8  )  9  ); 10 }

在上面的例子中,第一个实例的单词 ‘Recipe’ 是别名。它是关系的唯一标识,它可以是你选择的任何东西。通常你会选择与要引用的类相同的名字。然而,每个模型的别名在应用程序中必须唯一。合适的例子有:

 1 class User extends AppModel {  2 public $hasMany = array(  3 'MyRecipe' => array(  4 'className' => 'Recipe',  5  )  6  );  7 public $hasAndBelongsToMany => array(  8 'MemberOf' => array(  9 'className' => 'Group', 10  ) 11  ); 12 }
 1 class Group extends AppModel {  2 public $hasMany = array(  3 'MyRecipe' => array(  4 'className'  => 'Recipe',  5  )  6  );  7 public $hasAndBelongsToMany => array(  8 'Member' => array(  9 'className' => 'User', 10  ) 11  ); 12 }

但是在所有的情况下,以下代码都不工作:

 1 class User extends AppModel {  2 public $hasMany = array(  3 'MyRecipe' => array(  4 'className' => 'Recipe',  5  )  6  );  7 public $hasAndBelongsToMany => array(  8 'Member' => array(  9 'className' => 'Group', 10  ) 11  ); 12 }
 1 class Group extends AppModel {  2 public $hasMany = array(  3 'MyRecipe' => array(  4 'className'  => 'Recipe',  5  )  6  );  7 public $hasAndBelongsToMany => array(  8 'Member' => array(  9 'className' => 'User', 10  ) 11  ); 12 }

因为在 HABTM 关联中,别名 ‘Member’ 同时指向了 User 模型(在 Group 模型中)和 Group 模型(在 User 模型中)。 在不同的模型为某个模型起不唯一的别名,可能会带来未知的行为。

Cake 能自动在关联模型对象间建立连接。所以你可以在你的 User 模型中以如下方式访问 Recipe 模型:

1 $this->Recipe->someFunction();

同样的,你也能在控制器中循着模型关系访问关联模型:

1 $this->User->Recipe->someFunction();

注解

记住,关系定义是 ‘单向的’。如果你定义了 User hasMany Recipe,对 Recipe 模型是没有影响的。你需要定义 Recipe belongsTo User才能从 Recipe 模型访问 User 模型。

hasOne

让我们设置 User 模型以 hasOne 类型关联到 Profile 模型。

首先,数据库表需要有正确的主键。对于 hasOne 关系,一个表必须包含指向另一个表的记录的外键。在本例中,profiles 表将包含一个叫做 user_id 的列。基本模式是: :

hasOne: 另一个 模型包含外键。

关系 结构
Apple hasOne Banana bananas.apple_id
User hasOne Profile profiles.user_id
Doctor hasOne Mentor mentors.doctor_id

注解

关于这一点,并没有强制要求遵循 CakePHP 约定,你能够很容易地在关联定义中使用任何外键来覆盖它。虽然如此,遵守规则将使你的代码更简捷,更易于阅读和维护。

User 模型文件保存为 /app/Model/User.php。为了定义‘User hasOne Profile’ 关联,需要在模型类中添加 $hasOne属性。记得要在 /app/Model/Profile.php 文件中放一个 Profile 模型,否则关联将不工作:

1 class User extends AppModel { 2 public $hasOne = 'Profile'; 3 }

有两种途径在模型文件中描述此关系。简单的方法是设置一个包含要关联的模型的类名的字符串型属性 $hasOne,就像我们上面做的那样。

如果需要更全面的控制,可以使用数组语法定义关联。例如,你可能想要限制关联只包含某些记录。

1 class User extends AppModel { 2 public $hasOne = array( 3 'Profile' => array( 4 'className'    => 'Profile', 5 'conditions'   => array('Profile.published' => '1'), 6 'dependent'    => true 7  ) 8  ); 9 }

hasOne 关联数组可能包含的键有: :

  • className: 被关联到当前模型的模型类名。如果你定义了 ‘User hasOne Profile’关系,类名键将是 ‘Profile.’
  • foreignKey: 另一张表中的外键名。如果需要定义多个 hasOne 关系,这个键非常有用。其默认值为当前模型的单数模型名缀以 ‘_id’。在上面的例子中,就默认为 ‘user_id’。
  • conditions: 一个 find() 兼容条件的数组或者类似 array(‘Profile.approved’ => true) 的 SQL 字符串.
  • fields: 需要在匹配的关联模型数据中获取的列的列表。默认返回所有的列。
  • order: 一个 find() 兼容排序子句或者类似 array(‘Profile.last_name’ => ‘ASC’) 的 SQL 字符串。
  • dependent: 当 dependent 键被设置为 true,并且模型的 delete() 方法调用时的参数 cascade 被设置为 true,关联模型的记录同时被删除。在本例中,我们将其设置为 true 将导致删除一个 User 时同时删除与其相关的 Profile。

一旦定义了关系,User 模型上的 find 操作将匹配存在的关联 Profile 记录:

 1 // 调用 $this->User->find() 的示例结果。  2  3 Array  4 (  5 [User] => Array  6  (  7 [id] => 121  8 [name] => Gwoo the Kungwoo  9 [created] => 2007-05-01 10:31:01 10  ) 11 [Profile] => Array 12  ( 13 [id] => 12 14 [user_id] => 121 15 [skill] => Baking Cakes 16 [created] => 2007-05-01 10:31:01 17  ) 18 )

belongsTo

现在我们有了通过访问 User 模型获取相关 Profile 数据的办法,让我们在 Profile 模型中定义 belongsTo 关联以获取相关的 User 数据。belongsTo 关联是 hasOne 和 hasMany 关联的自然补充:它允许我们从其它途径查看数据。

在为 belongsTo 关系定义数据库表的键时,遵循如下约定:

belongsTo: 当前模型 包含外键。

关系 结构
Banana belongsTo Apple bananas.apple_id
Profile belongsTo User profiles.user_id
Mentor belongsTo Doctor mentors.doctor_id

小技巧

如果一个模型(表)包含一个外键,它 belongsTo 另一个模型(表)。

我们可以使用如下字符串语法,在 /app/Model/Profile.php 文件中的 Profile 模型中定义 belongsTo 关联:

1 class Profile extends AppModel { 2 public $belongsTo = 'User'; 3 }

我们还能使用数组语法定义特定的关系:

1 class Profile extends AppModel { 2 public $belongsTo = array( 3 'User' => array( 4 'className'    => 'User', 5 'foreignKey'   => 'user_id' 6  ) 7  ); 8 }

belongsTo 关联数组可能包含的键有:

  • className: 被关联到当前模型的模型类名。如果你定义了 ‘Profile belongsTo User’关系,类名键的值将为 ‘User.’

  • foreignKey: 当前模型中需要的外键。用于需要定义多个 belongsTo 关系。其默认值为另一模型的单数模型名缀以 ‘_id’。

  • conditions: 一个 find() 兼容条件的数组或者类似 array('User.active' => true) 的 SQL 字符串。

  • type: SQL 查询的 join 类型,默认为 Left,这不可能在所有情况下都符合你的需求,在你想要从主模型和关联模型获取全部内容或者什么都不要时很有用!(仅在某些条件下有效)。 (注:类型值必须是小写,例如:left, inner)

  • fields: 需要在匹配的关联模型数据中获取的列的列表。默认返回所有的列。

  • order: 一个 find() 兼容排序子句或者类似 array('User.username' => 'ASC') 的 SQL 字符串。

  • counterCache: 如果此键的值设置为 true,当你在做 “save()” 或者 “delete()” 操作时关联模型将自动递增或递减外键关联的表的 “[singular_model_name]_count” 列的值。如果它是一个字符串,则其将是计数用的列名。计数列的值表示关联行的数量。也可以通过使用数组指定多个计数缓存,键为列名,值为条件,例如:

    1 array( 2 'recipes_count' => true, 3 'recipes_published' => array('Recipe.published' => 1) 4 )

     

  • counterScope: 用于更新计数缓存列的可选条件数组。

一旦定义了关联,Profile 模型上的 find 操作将同时获取相关的 User 记录(如果它存在的话):

 1 //调用 $this->Profile->find() 的示例结果。  2  3 Array  4 (  5 [Profile] => Array  6  (  7 [id] => 12  8 [user_id] => 121  9 [skill] => Baking Cakes 10 [created] => 2007-05-01 10:31:01 11  ) 12 [User] => Array 13  ( 14 [id] => 121 15 [name] => Gwoo the Kungwoo 16 [created] => 2007-05-01 10:31:01 17  ) 18 )

hasMany

下一步:定义一个 “User hasMany Comment” 关联。一个 hasMany 关联将允许我们在获取 User 记录的同时获取用户的评论。

在为 hasMany 关系定义数据库表的键时,遵循如下约定:

hasMany: 其它 模型包含外键。

关系 结构
User hasMany Comment Comment.user_id
Cake hasMany Virtue Virtue.cake_id
Product hasMany Option Option.product_id

我们可以使用如下字符串语法,在 /app/Model/User.php 文件中的 User 模型中定义 hasMnay 关联:

1 class User extends AppModel { 2 public $hasMany = 'Comment'; 3 }

我们还能使用数组语法定义特定的关系:

 1 class User extends AppModel {  2 public $hasMany = array(  3 'Comment' => array(  4 'className'     => 'Comment',  5 'foreignKey'    => 'user_id',  6 'conditions'    => array('Comment.status' => '1'),  7 'order'         => 'Comment.created DESC',  8 'limit'         => '5',  9 'dependent'     => true 10  ) 11  ); 12 }

hasMany 关联数组可能包含的键有:

  • className: 被关联到当前模型的模型类名。如果你定义了 ‘User hasMany Comment’关系,类名键的值将为 ‘Comment.’。
  • foreignKey: 另一张表中的外键名。如果需要定义多个 hasMany 关系,这个键非常有用。其默认值为当前模型的单数模型名缀以 ‘_id’。
  • conditions: 一个 find() 兼容条件的数组或者类似 array(‘Comment.visible’ => true) 的 SQL 字符串。
  • order: 一个 find() 兼容排序子句或者类似 array(‘Profile.last_name’ => ‘ASC’) 的 SQL 字符串。
  • limit: 想返回的关联行的最大行数。
  • offset: 获取和关联前要跳过的行数(根据提供的条件 - 多数用于分页时的当前页的偏移量)。
  • dependent: 如果 dependent 设置为 true,就有可能进行模型的递归删除。在本例中,当 User 记录被删除后,关联的 Comment 记录将被删除。
  • exclusive: 当 exclusive 设置为 true,将用 deleteAll() 代替分别删除每个实体来来完成递归模型删除。这大大提高了性能,但可能不是所有情况下的理想选择。
  • finderQuery: CakePHP 中用于获取关联模型的记录的完整 SQL 查询。用在包含许多自定义结果的场合。 如果你建立的一个查询包含关联模型 ID 的引用,在查询中使用 $__cakeID__$} 标记它。例如,如果你的 Apple 模型 hasMany Orange,此查询看上去有点像这样: SELECT Orange.* from oranges as Orange WHEREOrange.apple_id = {$__cakeID__$};

一旦关联被建立,User 模型上的 find 操作也将获取相关的 Comment 数据(如果它存在的话):

 1 //调用 $this->User->find() 获得的结果示例。  2  3 Array  4 (  5 [User] => Array  6  (  7 [id] => 121  8 [name] => Gwoo the Kungwoo  9 [created] => 2007-05-01 10:31:01 10  ) 11 [Comment] => Array 12  ( 13 [0] => Array 14  ( 15 [id] => 123 16 [user_id] => 121 17 [title] => On Gwoo the Kungwoo 18 [body] => The Kungwooness is not so Gwooish 19 [created] => 2006-05-01 10:31:01 20  ) 21 [1] => Array 22  ( 23 [id] => 124 24 [user_id] => 121 25 [title] => More on Gwoo 26 [body] => But what of the ‘Nut? 27 [created] => 2006-05-01 10:41:01 28  ) 29  ) 30 )

有件事需要记住:你还需要定义 Comment belongsTo User 关联,用于从两个方向获取数据。 我们在这一节概述了能够使你从 User 模型获取 Comment 数据的方法。在 Comment 模型中添加 Comment belongsTo User 关系将使你能够从 Comment 模型中获取 User 数据 - 这样的链接关系才是完整的且允许从两个模型的角度获取信息流。

counterCache - 缓存你的 count()

这个功能帮助你缓存相关数据的计数。模型通过自己追踪指向关联 $hasMany 模型的所有的添加/删除并递增/递减父模型表的专用整数列,替代手工调用 find('count') 计算记录的计数。

这个列的名称由列的单数名后缀以下划线和单词 “count” 构成:

my_model_count 

如果你有一个叫 ImageComment 的模型和一个叫 Image 的模型,你需要添加一个指向 images 表的新的整数列并命名为image_comment_count

下面是更多的示例:

模型 关联模型 示例
User Image users.image_count
Image ImageComment images.image_comment_count
BlogEntry BlogEntryComment blog_entries.blog_entry_comment_count

一旦你添加了计数列,就可以使用它了。通过在你的关联中添加 counterCache 键并将其值设置为 true,可以激活 counter-cache:

1 class ImageComment extends AppModel { 2 public $belongsTo = array( 3 'Image' => array( 4 'counterCache' => true, 5  ) 6  ); 7 }

自此,你每次添加或删除一个关联到 Image 的 ImageCommentimage_comment_count 字段的数字都会自动调整。

你还可以指定 counterScope。它允许你指定一个简单的条件,通知模型什么时候更新(不更新)计数值,这依赖于你如何查看。

在我们的 Image 模型示例中,我们可以象下面这样指定:

1 class ImageComment extends AppModel { 2 
            var cpro_id = "u6885494";

        
        
    
推荐阅读
  • 解决phpMyAdmin运行错误:mysqli_init(): 属性访问尚未允许
    本文探讨了在使用phpMyAdmin过程中遇到的mysqli_init()函数错误,并提供了有效的解决方案。 ... [详细]
  • 本文探讨了在执行SQL查询时遇到的因字符集不同而导致查询结果差异的问题,特别是涉及中文字符时。文章分析了在不同字符集设置下,SQL查询结果的变化,并提供了详细的解决方案。 ... [详细]
  • 一、数据更新操作DML语法中主要包括两个内容:查询与更新,更新主要包括:增加数据、修改数据、删除数据。其中这些操作是离不开查询的。1、增加数据语法:INSERTINTO表名称[(字 ... [详细]
  • SQL注入实验:SqliLabs第38至45关解析
    本文深入探讨了SqliLabs项目中的第38至45关,重点讲解了堆叠注入(Stacked Queries)的应用技巧及防御策略。通过实际案例分析,帮助读者理解如何利用和防范此类SQL注入攻击。 ... [详细]
  • KKCMS代码审计初探
    本文主要介绍了KKCMS的安装过程及其基本功能,重点分析了该系统中存在的验证码重用、SQL注入及XSS等安全问题。适合初学者作为入门指南。 ... [详细]
  • 解析程序员与软件工程师的角色差异
    本文深入探讨了程序员与软件工程师之间的主要区别,包括它们的职业定位、技能要求以及工作内容等方面的不同,旨在帮助读者更好地理解这两个角色的特点。 ... [详细]
  • 开发笔记:新手DVWACSRF
    开发笔记:新手DVWACSRF ... [详细]
  • 应用程序配置详解
    本文介绍了配置文件的关键特性及其在不同场景下的应用,重点探讨了Machine.Config和Web.Config两种主要配置文件的用途和配置方法。文章还详细解释了如何利用XML格式的配置文件来调整应用程序的行为,包括自定义配置、错误处理、身份验证和授权设置。 ... [详细]
  • 本文详细探讨了在Windows Server 2003环境下遇到MySQL连接失败(错误代码10061)的解决方案,包括通过卸载特定的Windows更新和调整系统注册表设置的方法。 ... [详细]
  • 尝试从 MySQL 转向 SQL Server 2008 时遇到了安装错误,提示“系统配置未能初始化”,错误代码为 0x84B10001。怀疑可能是由于之前的 MySQL 安装残留导致的注册表问题。寻求专家建议。 ... [详细]
  • 使用DataGridViewComboBoxColumn实现数据绑定与操作
    本文详细介绍如何在DataGridView中使用DataGridViewComboBoxColumn来加载、选择和保存数据库中的数据,提供具体的实现步骤和示例代码。 ... [详细]
  • 本文详细介绍了如何在SQL查询中使用SAVEPOINT语句进行事务管理,以及如何通过多列排序增强查询结果的组织性。例如,通过在ORDER BY子句中指定多个列,可以实现更加细致和灵活的数据排序。 ... [详细]
  • Web安全入门:MySQL基础操作与SQL注入防范
    本文详细介绍了MySQL数据库的基础操作命令,包括数据库和表的基本管理,以及数据的增删查改等常用操作。同时,针对Web安全领域常见的SQL注入问题,提供了初步的理解和防范措施。 ... [详细]
  • 本文探讨了如何利用Oracle的REGEXP_SUBSTR函数高效地从复杂字符串中提取括号内的特定值,提供了详细的示例和解决方案。 ... [详细]
  • mysql 分库分表策略_【数据库】分库分表策略
    关系型数据库本身比较容易成为系统瓶颈,单机存储容量、连接数、处理能力都有限。当单表的数据量达到1000W或100G以后,由于查询维度较多, ... [详细]
author-avatar
相思和怀恋_811_372
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有