# 关联模型
[上一页](# "上一页")[下一页](# "下一页")
### 关联关系
通常我们所说的关联关系包括下面三种:
一对一关联 :ONE_TO_ONE,包括HAS_ONE和BELONGS_TO
一对多关联 :ONE_TO_MANY,包括HAS_MANY和BELONGS_TO
多对多关联 :MANY_TO_MANY
关联关系必然有一个参照表,例如:
有一个员工档案管理系统项目,这个项目要包括下面的一些数据表:基本信息表、员工档案表、部门表、项目组表、银行卡表(用来记录员工的银行卡资料)。
这些数据表之间存在一定的关联关系,我们以员工基本信息表为参照来分析和其他表之间的关联:
每个员工必然有对应的员工档案资料,所以属于HAS_ONE关联;
每个员工必须属于某个部门,所以属于BELONGS_TO关联;
每个员工可以有多个银行卡,但是每张银行卡只可能属于一个员工,因此属于HAS_MANY关联;
每个员工可以同时在多个项目组,每个项目组同时有多个员工,因此属于MANY_TO_MANY关联;
分析清楚数据表之前的关联关系后,我们才可以进行关联定义和关联操作。
### 关联定义
ThinkPHP可以很轻松的完成数据表的关联CURD操作,目前支持的关联关系包括下面四种:**HAS_ONE、BELONGS_TO、HAS_MANY和MANY_TO_MANY**。
一个模型根据业务模型的复杂程度可以同时定义多个关联,不受限制,所有的关联定义都统一在模型类的 $_link 成员变量里面定义,并且可以支持动态定义。要支持关联操作,模型类必须继承RelationModel类,关联定义的格式是:`protected $_link = array(
'关联1' => array(
'关联属性1' => '定义',
'关联属性N' => '定义',
),
'关联2' => array(
'关联属性1' => '定义',
'关联属性N' => '定义',
),
'关联3' => HAS_ONE, // 快捷定义
...
);`下面我们首先来分析下各个关联方式的定义:
**HAS_ONE**
HAS_ONE关联表示当前模型拥有一个子对象,例如,每个员工都有一个人事档案。我们可以建立一个用户模型UserModel,并且添加如下关联定义:`class UserModel extends RelationModel{
protected $_link = array(
'Profile'=> HAS_ONE,
);
}`上面是最简单的方式,表示其遵循了系统内置的数据库规范,完整的定义方式是:`class UserModel extends RelationModel{
protected $_link = array(
'Profile'=>array(
'mapping_type' =>HAS_ONE,
'class_name' =>'Profile',
// 定义更多的关联属性
……
),
);
}`关联HAS_ONE支持的关联属性有:
<table border="0" cellspacing="1" cellpadding="0"><tr><td><b>mapping_type</b> </td><td width="450">关联类型,这个在HAS_ONE 关联里面必须使用HAS_ONE 常量定义。</td></tr><tr><td><b>class_name</b> </td><td>要关联的模型类名<br/>例如,class_name 定义为Profile的话则表示和另外的Profile模型类关联,这个Profile模型类是无需定义的,系统会自动定位到相关的数据表进行关联。</td></tr><tr><td><b>mapping_name</b> </td><td>关联的映射名称,用于获取数据用<br/>该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。如果mapping_name没有定义的话,会取class_name的定义作为mapping_name。如果class_name也没有定义,则以数组的索引作为mapping_name。</td></tr><tr><td><b>foreign_key</b> </td><td>关联的外键名称<br/>外键的默认规则是当前数据对象名称_id,例如:<br/>UserModel对应的可能是表think_user (注意:think只是一个表前缀,可以随意配置)<br/>那么think_user表的外键默认为 user_id,如果不是,就必须在定义关联的时候显式定义 foreign_key 。</td></tr><tr><td><b>condition</b> </td><td>关联条件<br/>关联查询的时候会自动带上外键的值,如果有额外的查询条件,可以通过定义关联的condition属性。</td></tr><tr><td><b>mapping_fields</b> </td><td>关联要查询的字段<br/>默认情况下,关联查询的关联数据是关联表的全部字段,如果只是需要查询个别字段,可以定义关联的mapping_fields属性。</td></tr><tr><td><b>as_fields</b></td><td>直接把关联的字段值映射成数据对象中的某个字段<br/>这个特性是ONE_TO_ONE 关联特有的,可以直接把关联数据映射到数据对象中,而不是作为一个关联数据。当关联数据的字段名和当前数据对象的字段名称有冲突时,还可以使用映射定义。</td></tr></table>
**BELONGS_TO**
Belongs_to 关联表示当前模型从属于另外一个父对象,例如每个用户都属于一个部门。我们可以做如下关联定义。` 'Dept'=> BELONGS_TO`完整方式定义为:
`'Dept'=> array(
'mapping_type'=>BELONGS_TO,
'class_name'=>'Dept',
'foreign_key'=>'userId',
'mapping_name'=>'dept',
// 定义更多的关联属性
……
),`关联BELONGS_TO定义支持的关联属性有:
<table border="0" cellspacing="1" cellpadding="0"><tr><td>class_name</td><td> 要关联的模型类名</td></tr><tr><td>mapping_name </td><td>关联的映射名称,用于获取数据用<br/>该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。</td></tr><tr><td>foreign_key </td><td>关联的外键名称</td></tr><tr><td>mapping_fields</td><td> 关联要查询的字段</td></tr><tr><td>condition</td><td> 关联条件</td></tr><tr><td>parent_key</td><td> 自引用关联的关联字段<br/>默认为parent_id <br/>自引用关联是一种比较特殊的关联,也就是关联表就是当前表。</td></tr><tr><td>as_fields</td><td>直接把关联的字段值映射成数据对象中的某个字段</td></tr></table>
**HAS_MANY**
HAS_MANY 关联表示当前模型拥有多个子对象,例如每个用户有多篇文章,我们可以这样来定义:`'Article'=> HAS_MANY`完整定义方式为:
`'Article'=> array(
'mapping_type'=>HAS_MANY,
'class_name'=>'Article',
'foreign_key'=>'userId',
'mapping_name'=>'articles',
'mapping_order'=>'create_time desc',
// 定义更多的关联属性
……
),`关联HAS_MANY定义支持的关联属性有:
<table border="0" cellspacing="1" cellpadding="0"><tr><td>class_name</td><td>要关联的模型类名</td></tr><tr><td>mapping_name</td><td> 关联的映射名称,用于获取数据用<br/>该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。</td></tr><tr><td>foreign_key</td><td> 关联的外键名称<br/>外键的默认规则是当前数据对象名称_id,例如:<br/>UserModel对应的可能是表think_user (注意:think只是一个表前缀,可以随意配置)<br/>那么think_user表的外键默认为 user_id,如果不是,就必须在定义关联的时候定义 foreign_key 。</td></tr><tr><td>parent_key</td><td> 自引用关联的关联字段<br/>默认为parent_id </td></tr><tr><td>condition</td><td> 关联条件<br/>关联查询的时候会自动带上外键的值,如果有额外的查询条件,可以通过定义关联的condition属性。</td></tr><tr><td>mapping_fields</td><td> 关联要查询的字段<br/>默认情况下,关联查询的关联数据是关联表的全部字段,如果只是需要查询个别字段,可以定义关联的mapping_fields属性。</td></tr><tr><td>mapping_limit</td><td> 关联要返回的记录数目</td></tr><tr><td>mapping_order</td><td> 关联查询的排序</td></tr></table>
**MANY_TO_MANY**
MANY_TO_MANY 关联表示当前模型可以属于多个对象,而父对象则可能包含有多个子对象,通常两者之间需要一个中间表类约束和关联。例如每个用户可以属于多个组,每个组可以有多个用户:
`'Group'=>MANY_TO_MANY`完整定义方式为:
`array('mapping_type'=>MANY_TO_MANY,
'class_name'=>'Group',
'mapping_name'=>'groups',
'foreign_key'=>'userId',
'relation_foreign_key'=>'goupId',
'relation_table'=>'think_gourpUser'
)`MANY_TO_MANY支持的关联属性定义有:
<table border="0" cellspacing="1" cellpadding="0"><tr><td>class_name</td><td>要关联的模型类名</td></tr><tr><td>mapping_name</td><td> 关联的映射名称,用于获取数据用<br/>该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。</td></tr><tr><td>foreign_key</td><td> 关联的外键名称<br/>外键的默认规则是当前数据对象名称_id</td></tr><tr><td>relation_foreign_key</td><td> 关联表的外键名称<br/>默认的关联表的外键名称是表名_id</td></tr><tr><td>mapping_limit</td><td> 关联要返回的记录数目</td></tr><tr><td>mapping_order</td><td> 关联查询的排序</td></tr><tr><td>relation_table</td><td>多对多的中间关联表名称</td></tr></table>
多对多的中间表默认表规则是:数据表前缀_关联操作的主表名_关联表名
如果think_user 和 think_group 存在一个对应的中间表,默认的表名应该是
如果是由group来操作关联表,中间表应该是 think_group_user,如果是从user表来操作,那么应该是think_user_group,也就是说,多对多关联的设置,必须有一个Model类里面需要显式定义中间表,否则双向操作会出错。
中间表无需另外的id主键(但是这并不影响中间表的操作),通常只是由 user_id 和 group_id 构成。
默认会通过当前模型的getRelationTableName方法来自动获取,如果当前模型是User,关联模型是Group,那么关联表的名称也就是使用 user_group这样的格式,如果不是默认规则,需要指定relation_table属性。
### 关联查询
由于性能问题,新版取消了自动关联查询机制,而统一使用relation方法进行关联操作,relation方法不但可以启用关联还可以控制局部关联操作,实现了关联操作一切尽在掌握之中。`$User = D("User");
$user = $User->relation(true)->find(1);`输出$user结果可能是类似于下面的数据:`array(
'id' => 1,
'account' => 'ThinkPHP',
'password' => '123456',
'Profile' => array(
'email' =>'liu21st@gmail.com',
'nickname' =>'流年',
),
)`我们可以看到,用户的关联数据已经被映射到数据对象的属性里面了。其中Profile就是关联定义的mapping_name属性。
如果我们按照下面的方式定义了as_fields属性的话,` protected $_link = array(
'profile'=>array(
'mapping_type' =>HAS_ONE,
'class_name' =>'Profile',
'foreign_key'=>'userId',
'as_fields'=>'email,nickname',
),
);`查询的结果就变成了下面的结果`array(
'id' => 1,
'account' => 'ThinkPHP',
'password' => 'name',
'email' =>'liu21st@gmail.com',
'nickname' =>'流年',
)`email和nickname两个字段已经作为user数据对象的字段来显示了。
如果关联数据的字段名和当前数据对象的字段有冲突的话,怎么解决呢?
我们可以用下面的方式来变化下定义:
'as_fields'=>'email,nickname:username',
表示关联表的nickname字段映射成当前数据对象的username字段。
默认会把所有定义的关联数据都查询出来,有时候我们并不希望这样,就可以给relation方法传入参数来控制要关联查询的。`$User = D("User");
$user = $User->relation('Profile')->find(1);`关联查询一样可以支持select方法,如果要查询多个数据,并同时获取相应的关联数据,可以改成:`$User = D("User");
$list = $User->relation(true)->Select();`如果希望在完成的查询基础之上 再进行关联数据的查询,可以使用`$User = D("User");
$user = $User->find(1);
// 表示对当前查询的数据对象进行关联数据获取
$profile = $User->relationGet("Profile");`事实上,除了当前的参考模型User外,其他的关联模型是不需要创建的。
### 关联操作
除了关联查询外,系统也支持关联数据的自动写入、更新和删除
**关联写入**`$User = D("User");
$data = array();
$data["account"] = "ThinkPHP";
$data["password"] = "123456";
$data["Profile"] = array(
'email' =>'liu21st@gmail.com',
'nickname' =>'流年',
);
$result =$User->relation(true)->add($data);`这样就会自动写入关联的Profile数据。
同样,可以使用参数来控制要关联写入的数据:`$result = $User->relation("Profile")->add($data);`**关联更新**
数据的关联更新和关联写入类似`$User = D("User");
$data["account"] = "ThinkPHP";
$data["password"] = "123456";
$data["Profile"] = array(
'email' =>'liu21st@gmail.com',
'nickname' =>'流年',
);
$result = $User-> relation(true)->where('id=3')->save($data);`Relation(true)会关联保存User模型定义的所有关联数据,如果只需要关联保存部分数据,可以使用:`$result = $User->relation("Profile")->save($data);`这样就只会同时更新关联的Profile数据。
关联保存的规则:
HAS_ONE: 关联数据的更新直接赋值
HAS_MANY: 的关联数据如果传入主键的值 则表示更新 否则就表示新增
MANY_TO_MANY: 的数据更新是删除之前的数据后重新写入**关联删除**
删除用户ID为3的记录的同时删除关联数据`$result = $User->relation(true)->delete("3");`如果只需要关联删除部分数据,可以使用`$result = $User->relation("Profile")->delete("3");`
[上一页](# "上一页")[下一页](# "下一页")
- 序言
- 1. 入门
- 1.1 简介
- 1.2 基础概念
- 1.3 获取ThinkPHP
- 1.4 环境要求
- 1.5 许可协议
- 1.6 目录结构
- 1.7 命名规范
- 1.8 MVC分层
- 1.9 CBD架构
- 1.10 特性概述
- 1.11 系统流程
- 1.12 开发流程
- 2. 入口
- 2.1 入口文件
- 2.2 项目目录
- 2.3 部署目录
- 2.4 项目编译
- 2.5 调试模式
- 3. 配置
- 3.1 配置格式
- 3.2 惯例配置
- 3.3 项目配置
- 3.4 调试配置
- 3.5 分组配置
- 3.6 读取配置
- 3.7 动态配置
- 3.8 扩展配置
- 4. 函数和类库
- 4.1 函数库
- 4.2 类库
- 5. 控制器
- 5.1 URL模式
- 5.2 模块和操作
- 5.3 定义控制器
- 5.4 空操作
- 5.5 空模块
- 5.6 模块分组
- 5.7 URL伪静态
- 5.8 URL路由
- 5.9 URL重写
- 5.10 URL生成
- 5.11 URL大小写
- 5.12 前置和后置操作
- 5.13 跨模块调用
- 5.14 页面跳转
- 5.15 重定向
- 5.16 获取系统变量
- 5.17 判断请求类型
- 5.18 获取URL参数
- 5.19 AJAX返回
- 5.20 Action参数绑定
- 5.21 多层控制器支持
- 6. 模型
- 6.1 模型定义
- 6.2 模型实例化
- 6.3 字段定义
- 6.4 数据主键
- 6.5 属性访问
- 6.6 跨库操作
- 6.7 连接数据库
- 6.8 切换数据库
- 6.9 分布式数据库
- 6.10 创建数据
- 6.11 字段映射
- 6.12 连贯操作
- 6.13 CURD操作
- 6.14 ActiveRecord
- 6.15 自动验证
- 6.16 命名范围
- 6.17 自动完成
- 6.18 查询语言
- 6.19 查询锁定
- 6.20 字段排除
- 6.21 事务支持
- 6.22 高级模型
- 6.23 视图模型
- 6.24 关联模型
- 6.25 Mongo模型
- 6.26 动态模型
- 6.27 虚拟模型
- 6.28 多层模型支持
- 7. 视图
- 7.1 模板定义
- 7.2 模板赋值
- 7.3 模板输出
- 7.4 模板替换
- 7.5 获取内容
- 7.6 模板引擎
- 7.7 布局模板
- 8. 模板引擎
- 8.1 变量输出
- 8.2 系统变量
- 8.3 使用函数
- 8.4 默认值输出
- 8.5 使用运算符
- 8.6 内置标签
- 8.7 包含文件
- 8.8 导入文件
- 8.9 Volist标签
- 8.10 Foreach标签
- 8.11 For标签
- 8.12 Switch标签
- 8.13 比较标签
- 8.14 三元运算
- 8.15 范围判断标签
- 8.16 Present标签
- 8.17 Empty标签
- 8.18 Defined标签
- 8.19 Define标签
- 8.20 Assign标签
- 8.21 IF标签
- 8.22 标签嵌套
- 8.23 使用PHP代码
- 8.24 模板布局
- 8.25 模板继承
- 8.26 原样输出
- 8.27 模板注释
- 8.28 引入标签库
- 8.29 修改定界符
- 8.30 避免JS混淆
- 9. 日志
- 9.1 日志级别
- 9.2 记录方式
- 9.3 手动记录
- 10. 错误
- 10.1 异常处理
- 10.2 异常模板
- 10.3 异常显示
- 11. 调试
- 11.1 运行状态
- 11.2 页面Trace
- 11.3 调试方法
- 12. 缓存
- 12.1 缓存方式
- 12.2 动态缓存
- 12.3 缓存队列
- 12.4 快捷缓存
- 12.5 快速缓存
- 12.6 查询缓存
- 12.7 SQL解析缓存
- 12.8 静态缓存
- 13. 扩展
- 13.1 行为扩展
- 13.2 类库扩展
- 13.3 控制器扩展
- 13.4 模型扩展
- 13.5 驱动扩展
- 13.6 Widget扩展
- 13.7 模式扩展
- 13.8 引擎扩展
- 14. 安全
- 14.1 表单令牌
- 14.2 字段类型验证
- 14.3 防止SQL注入
- 14.4 输入过滤
- 14.5 上传安全
- 14.6 防止XSS攻击
- 14.7 其他安全建议
- 14.8 目录安全文件
- 14.9 保护模板文件
- 15. 性能
- 15.1 关闭调试模式
- 15.2 开启缓存
- 15.3 合并字段缓存
- 15.4 优化SQL
- 15.5 替换入口
- 15.6 前端优化
- 16. 部署
- 16.1 PATH_INFO支持
- 16.2 隐藏index.php
- 16.3 二级域名部署
- 16.4 定制错误页面
- 16.5 设置时区
- 17. SAE支持
- 17.1 SAE介绍
- 17.2 获取SAE
- 17.3 SAE开发
- 18. REST支持
- 18.1 REST介绍
- 18.2 REST模式
- 18.3 REST配置
- 18.4 REST路由
- 18.5 REST方法
- 19. 杂项
- 19.1 Session支持
- 19.2 Cookie支持
- 19.3 日期和时间
- 19.4 WML开发
- 19.5 多语言
- 19.6 数据分页
- 19.7 文件上传
- 19.8 验证码
- 19.9 图片添加水印
- 19.10 IP获取和定位
- 20. 附录
- 20.1 常量参考
- 20.2 配置参考
- 20.3 关于升级
- 20.4 大事记
- 鸣谢
- 关于