CakePHP 2.x CookBook 中文版 第七章 模型 之 关联:将模型连接在一起
关联:将模型连接在一起
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 的 ImageComment,image_comment_count 字段的数字都会自动调整。
你还可以指定 counterScope。它允许你指定一个简单的条件,通知模型什么时候更新(不更新)计数值,这依赖于你如何查看。
在我们的 Image 模型示例中,我们可以象下面这样指定:
1 class ImageComment extends AppModel { 2 public $belongsTo = array( 3 'Image' => array( 4 'counterCache' => true, 5 'counterScope' => array('Image.active' => 1) // only count if "Image" is active = 1 6 ) 7 ); 8 }
hasAndBelongsToMany (HABTM)
现在,你已经是 CakePHP 模型关联的专家了。你已经深谙对象关系中的三种关联。
现在我们来解决最后一种关系类型: hasAndBelongsToMany,也称为 HABTM。 这种关联用于两个模型需要多次重复以不同方式连接的场合。
hasMany 与 HABTM 主要不同点是 HABTM 中对象间的连接不是唯一的。例如,以 HABTM 方式连接 Recipe 模型和 Ingredient 模型。西红柿不只可以作为我奶奶意大利面(Recipe)的成分(Ingredient),我也可以用它做色拉(Recipe)。
hasMany 关联对象间的连接是唯一的。如果我们的 User hasMnay Comments,一个评论仅连接到一个特定的用户。它不能被再利用。
继续前进。我们需要在数据库中设置一个额外的表,用来处理 HABTM 关联。这个新连接表的名字需要包含两个相关模型的名字,按字母顺序并且用下划线(_)间隔。表的内容有两个列,每个外键(整数类型)都指向相关模型的主键。为避免出现问题 - 不要为这个两个列定义复合主键,如果应用程序包含复合主键,你可以定义一个唯一的索引(作为外键指向的键)。如果你计划在这个表中加入任何额外的信息,或者使用 ‘with’ 模型,你需要添加一个附加主键列(约定为 ‘id’)
HABTM 包含一个单独的连接表,其表名包含两个 模型 的名字。
关系 | HABTM 表列 |
---|---|
Recipe HABTM Ingredient | ingredients_recipes.id, ingredients_recipes.ingredient_id,ingredients_recipes.recipe_id |
Cake HABTM Fan | cakes_fans.id, cakes_fans.cake_id, cakes_fans.fan_id |
Foo HABTM Bar | bars_foos.id, bars_foos.foo_id, bars_foos.bar_id |
注解
按照约定,表名是按字母顺序组成的。在关联定义中自定义表名是可能的。
确保表 cakes 和 recipes 遵循了约定,由表中的 id 列担当主键。如果它们与假定的不同,模型的 主键 必须被改变。
一旦这个新表被建立,我们就可以在模型文件中建立 HABTM 关联了。这次我们将直接跳到数组语法:
1 class Recipe extends AppModel { 2 public $hasAndBelongsToMany = array( 3 'Ingredient' => 4 array( 5 'className' => 'Ingredient', 6 'joinTable' => 'ingredients_recipes', 7 'foreignKey' => 'recipe_id', 8 'associationForeignKey' => 'ingredient_id', 9 'unique' => true, 10 'conditions' => '', 11 'fields' => '', 12 'order' => '', 13 'limit' => '', 14 'offset' => '', 15 'finderQuery' => '', 16 'deleteQuery' => '', 17 'insertQuery' => '' 18 ) 19 ); 20 }
HABTM 关联数组可能包含的键有:
-
className: 关联到当前模型的模型类名。如果你定义了 ‘Recipe HABTM Ingredient’ 关系,这个类名将是 ‘Ingredient.’
-
joinTable: 在本关联中使用的连接表的名字(如果当前表没有按照 HABTM 连接表的约定命名的话)。
-
with: 为连接表定义模型名。默认的情况下,CakePHP 将自动为你建立一个模型。上例中,它被称为 IngredientsRecipe。你可以使用这个键来覆盖默认的名字。连接表模型能够像所有的 “常规” 模型那样用来直接访问连接表。通过建立带有相同类名和文件名的模型类,你可以向连接表搜索中加入任何自定义行为,例如向其加入更多的信息/列。
-
foreignKey: 当前模型中需要的外键。用于需要定义多个 HABTM 关系。其默认值为当前模型的单数模型名缀以 ‘_id’。
-
associationForeignKey: 另一张表中的外键名。用于需要定义多个 HABTM 关系。其默认值为另一模型的单数模型名缀以 ‘_id’。
- unique: 布尔值或者字符串 keepExisting。
-
- 如果为 true (默认值),Cake 将在插入新行前删除外键表中存在的相关记录。现有的关系在更新时需要再次传递。
- 如果为 false,Cake 将插入相关记录,并且在保存过程中不删除连接记录。
- 如果设置为 keepExisting,其行为与 true 相同,但现有关联不被删除。
-
conditions: 个 find() 兼容条件的数组或者 SQL 字符串。如果在关联表上设置了条件,需要使用 ‘with’ 模型,并且在其上定义必要的 belongsTo 关联。
-
fields: 需要在匹配的关联模型数据中获取的列的列表。默认返回所有的列。
-
order: 一个 find() 兼容排序子句或者 SQL 字符串。
-
limit: 想返回的关联行的最大行数。
-
offset: 获取和关联前要跳过的行数(根据提供的条件 - 多数用于分页时的当前页的偏移量)。
-
finderQuery, deleteQuery, insertQuery: CakePHP 能用来获取、删除或者建立新的关联模型记录的完整 SQL 查询语句。用在包含很多自定义结果的场合。
一旦关联被创建,Recipe 模型上的 find 操作将可同时获取到相关的 Tag 记录(如果它们存在的话):
1 // 调用 $this->Recipe->find() 的结果示例。 2 3 Array 4 ( 5 [Recipe] => Array 6 ( 7 [id] => 2745 8 [name] => Chocolate Frosted Sugar Bombs 9 [created] => 2007-05-01 10:31:01 10 [user_id] => 2346 11 ) 12 [Ingredient] => Array 13 ( 14 [0] => Array 15 ( 16 [id] => 123 17 [name] => Chocolate 18 ) 19 [1] => Array 20 ( 21 [id] => 124 22 [name] => Sugar 23 ) 24 [2] => Array 25 ( 26 [id] => 125 27 [name] => Bombs 28 ) 29 ) 30 )
如果在使用 Ingredient 模型时想获取 Recipe 数据,记得在 Ingredient 模型中定义 HABTM 关联。
注解
HABTM 数据被视为完整的数据集。每次一个新的关联数据被加入,数据库中的关联行的完整数据集被删除并重新建立。所以你总是需要为保存操作传递整个数据集。使用 HABTM 的另一方法参见 hasMany 贯穿 (连接模型)
小技巧
关于保存 HABTM 对象的更多信息请参见: 保存相关模型数据 (HABTM)
hasMany 贯穿 (连接模型)
有时候需要存储带有多对多关系的附加数据。考虑以下情况:
Student hasAndBelongsToMany Course
Course hasAndBelongsToMany Student
换句话说,一个 Student 可以有很多 Courses,而一个 Course 也能有多个 Student。 这个简单的多对多关联需要一个类似于如下结构的表:
id | student_id | course_id
现在,如果我们要存储学生在课程上出席的天数及他们的最终级别,这张表将变成:
id | student_id | course_id | days_attended | grade
问题是,hasAndBelongsToMany 不支持这类情况,因为 hasAandBelongsToMany 关联被存储时,先要删除这个关联。列中的额外数据会丢失,且放到新插入的数据中。
在 2.1 版更改.
你可以将 unique 设置为 keepExisting 防止在保存过程丢失额外的数据。参阅 HABTM association arrays 中的 unique 键。
实现我们的要求的方法是使用一个 连接模型,或者也称为 hasMany 贯穿 关联。 具体作法是模型与自身关联。现在我们建立一个新的模型 CourseMembership。下面是此模型的定义。
1 // Student.php 2 class Student extends AppModel { 3 public $hasMany = array( 4 'CourseMembership' 5 ); 6 } 7 8 // Course.php 9 10 class Course extends AppModel { 11 public $hasMany = array( 12 'CourseMembership' 13 ); 14 } 15 16 // CourseMembership.php 17 18 class CourseMembership extends AppModel { 19 public $belongsTo = array( 20 'Student', 'Course' 21 ); 22 }
CourseMembership 连接模型唯一标识了一个给定的学生额外参与的课程,存入扩展元信息中。
连接表非常有用,Cake 使其非常容易地与内置的 hasMany 和 belongsTo 关联及 saveAll 特性一同使用。
在运行期间创建和销毁关联
有时候需要在运行时建立和销毁模型关联。比如以下几种情况:
- 你想减少获取的关联数据的量,但是你的所有关联都是循环的第一级。
- 你想要改变定义关联的方法以便排序或者过滤关联数据。
这种关联的建立与销毁由 CakePHP 模型 bindModel() 和 unbindModel() 方法完成。(还有一个非常有用的行为叫 “Containable”,更多信息请参阅手册中 内置行为 一节)。 我们来设置几个模型,看看 bindModel() 和 unbindModel() 如何工作。我们从两个模型开始:
1 class Leader extends AppModel { 2 public $hasMany = array( 3 'Follower' => array( 4 'className' => 'Follower', 5 'order' => 'Follower.rank' 6 ) 7 ); 8 }
1 class Follower extends AppModel { 2 public $name = 'Follower'; 3 }
现在,在 LeaderController 控制器中,我们能够使用 Leader 模型的 find() 方法获取一个 Leader 和它的 追随者(followers)。就像你上面看到的那样,Leader 模型的关联关系数组定义了 “Leader hasMany Followers” 关系。为了演示一下实际效果,我们使用 unbindModel() 删除控制器动作中的关联:
1 public function some_action() { 2 // 获取 Leaders 及其相关的 Followers 3 $this->Leader->find('all'); 4 5 // 删除 hasMany... 6 $this->Leader->unbindModel( 7 array('hasMany' => array('Follower')) 8 ); 9 10 // 现在使用 find 函数将只返回 Leaders,没有 Followers 11 $this->Leader->find('all'); 12 13 // NOTE: unbindModel 只影响紧随其后的 find 函数。再往后的 find 调用仍将使用预配置的关联信息。 14 15 // 我们已经在 unbindModel() 之后使用了 find('all'), 16 // 所以此处将再次获取 Leaders 及与其相关的 Followers ... 17 $this->Leader->find('all'); 18 }
注解
使用 bindModel() 和 unbindModel() 来添加和删除关联,仅在紧随其后的 find 操作中有效,除非第二个参数设置为 false。如果第二个参数被设置为 false,请求的剩余位置仍将保持 bind 行为。
以下是 unbindModel() 的基本用法模板:
1 $this->Model->unbindModel( 2 array('associationType' => array('associatedModelClassName')) 3 );
现在我们成功地在运行过程中删除了一个关联。 让我们来添加一个。我们到今天仍没有原则的领导需要一些关联的原则。我们的 Principle 模型文件除了 public $name 声明之外,什么都没有。 我们在运行中给我们的领导关联一些 Principles(谨记它仅在紧随其后的 find 操作中有效)。在 LeadersController 中的函数如下:
1 public function another_action() { 2 // 在 leader.php 文件中没有 Leader hasMany Principles 关联,所以这里的 find 只获取了 Leaders。 3 $this->Leader->find('all'); 4 5 // 我们来用 bindModel() 为 Leader 模型添加一个新的关联: 6 $this->Leader->bindModel( 7 array('hasMany' => array( 8 'Principle' => array( 9 'className' => 'Principle' 10 ) 11 ) 12 ) 13 ); 14 15 // 现在我们已经正确的设置了关联,我们可以使用单个的 find 函数来获取带有相关 principles 的 Leader: 16 $this->Leader->find('all'); 17 }
bindModel() 的基本用法是封装在以你尝试建立的关联类型命名的数组中的常规数组:
1 $this->Model->bindModel( 2 array('associationName' => array( 3 'associatedModelClassName' => array( 4 // normal association keys go here... 5 ) 6 ) 7 ) 8 );
即使不需要通过绑定模型对模型文件中的关联定义做任何排序,仍然需要为使新关联正常工作设置正确的排序键。
同一模型上的多个关系
有时一个模型有多个与其它模型的关联。例如,你可能需要有一个拥有两个 User 模型的 Message 模型。一个是要向其发送消息的用户,一个是从其接收消息的用户。 消息表有一个 user_id 列,还有一个 recipient_id。 你的消息模型看起来就像下面这样:
1 class Message extends AppModel { 2 public $belongsTo = array( 3 'Sender' => array( 4 'className' => 'User', 5 'foreignKey' => 'user_id' 6 ), 7 'Recipient' => array( 8 'className' => 'User', 9 'foreignKey' => 'recipient_id' 10 ) 11 ); 12 }
Recipient 是 User 模型的别名。来瞧瞧 User 模型是什么样的:
1 class User extends AppModel { 2 public $hasMany = array( 3 'MessageSent' => array( 4 'className' => 'Message', 5 'foreignKey' => 'user_id' 6 ), 7 'MessageReceived' => array( 8 'className' => 'Message', 9 'foreignKey' => 'recipient_id' 10 ) 11 ); 12 }
它也可以建立如下的自关联:
1 class Post extends AppModel { 2 3 public $belongsTo = array( 4 'Parent' => array( 5 'className' => 'Post', 6 'foreignKey' => 'parent_id' 7 ) 8 ); 9 10 public $hasMany = array( 11 'Children' => array( 12 'className' => 'Post', 13 'foreignKey' => 'parent_id' 14 ) 15 ); 16 }
获取关联记录的嵌套数组:
如果表里有 parent_id 使用不带任何关联设置的单个查询的 find(‘threaded’) 来获取记录的嵌套数组。
连接表
在 SQL 中你可以使用 JOIN 子句绑定相关表。 这允许你运行跨越多个表的复杂查询(例如:按给定的几个 tag 搜索帖子)。
在 CakePHP 中一些关联(belongsTo 和 hasOne)自动执行 join 以检索数据,所以你能发出根据相关数据检索模型的查询。
但是这不适用于 hasMany 和 hasAndBelongsToMany 关联。这些地方需要强制向循环中添加 join。你必须定义与要联合的表的必要连接(join),使你的查询获得期望的结果。
注解
谨记,你需要将 recursion 设置为 -1,以使其正常工作。例如: $this->Channel->recursive = -1;
在表间强制添加 join 时,你需要在调用 Model::find() 时使用 “modern” 语法,在 $options 数组中添加 ‘joins’ 键。例如:
1 $options['joins'] = array( 2 array('table' => 'channels', 3 'alias' => 'Channel', 4 'type' => 'LEFT', 5 'conditions' => array( 6 'Channel.id = Item.channel_id', 7 ) 8 ) 9 ); 10 11 $Item->find('all', $options);
注解
注意 ‘join’ 数组不是一个键。
在上面的例子中,叫做 Item 的模型 left join 到 channels 表。你可以用模型名为表起别名,以使检索到的数组完全符合 CakePHP 的数据结构。
定义 join 所用的键如下:
- table: 要连接的表。
- alias: 表的别名。最好使用关联模型名。
- type: 连接类型: inner, left 或者 right。
- conditions: 执行 join 的条件。
对于 joins 选项,你可以添加基于关系模型列的条件:
1 $options['joins'] = array( 2 array('table' => 'channels', 3 'alias' => 'Channel', 4 'type' => 'LEFT', 5 'conditions' => array( 6 'Channel.id = Item.channel_id', 7 ) 8 ) 9 ); 10 11 $options['conditions'] = array( 12 'Channel.private' => 1 13 ); 14 15 $privateItems = $Item->find('all', $options);
你可以在 hasAndBelongsToMany 中运行几个需要的 joins:
假定一个 Book hasAndBelongsToMany Tag。这个关系使用一个 books_tags 表合为连接表,你需要连接 books 表和 books_tags 表,并且带着 tags 表::
1 $options['joins'] = array( 2 array('table' => 'books_tags', 3 'alias' => 'BooksTag', 4 'type' => 'inner', 5 'conditions' => array( 6 'Books.id = BooksTag.books_id' 7 ) 8 ), 9 array('table' => 'tags', 10 'alias' => 'Tag', 11 'type' => 'inner', 12 'conditions' => array( 13 'BooksTag.tag_id = Tag.id' 14 ) 15 ) 16 ); 17 18 $options['conditions'] = array( 19 'Tag.tag' => 'Novel' 20 ); 21 22 $books = $Book->find('all', $options);
使用 joins 允许你以极为灵活的方式处理 CakePHP 的关系并获取数据,但是在很多情况下,你能使用其它工具达到同样的目的,例如正确地定义关联,运行时绑定模型或者使用 Containable 行为。使用这种特性要很小心,因为它在某些情况下可能会带来模式不规范的 SQL 查询。