关联关系

通常我们所说的关联关系包括下面三种:

大理石平台等级

  1. 一对一关联 :ONE_TO_ONE,包括HAS_ONE 和 BELONGS_TO
  2. 一对多关联 :ONE_TO_MANY,包括HAS_MANY 和 BELONGS_TO
  3. 多对多关联 :MANY_TO_MANY

关联关系必然有一个参照表,例如:

  • 有一个员工档案管理系统项目,这个项目要包括下面的一些数据表:基本信息表、员工档案表、部门表、项目组表、银行卡表(用来记录员工的银行卡资料)。
  • 这些数据表之间存在一定的关联关系,我们以员工基本信息表为参照来分析和其他表之间的关联:
  • 每个员工必然有对应的员工档案资料,所以属于HAS_ONE关联;
  • 每个员工必须属于某个部门,所以属于BELONGS_TO关联;
  • 每个员工可以有多个银行卡,但是每张银行卡只可能属于一个员工,因此属于HAS_MANY关联;
  • 每个员工可以同时在多个项目组,每个项目组同时有多个员工,因此属于MANY_TO_MANY关联;
  • 分析清楚数据表之前的关联关系后,我们才可以进行关联定义和关联操作。

关联定义

ThinkPHP可以很轻松的完成数据表的关联CURD操作,目前支持的关联关系包括下面四种:

HAS_ONEBELONGS_TOHAS_MANYMANY_TO_MANY

一个模型根据业务模型的复杂程度可以同时定义多个关联,不受限制,所有的关联定义都统一在模型类的 $_link 成员变量里面定义,并且可以支持动态定义。要支持关联操作,模型类必须继承Think\Model\RelationModel类,关联定义的格式是:

  1. namespace Home\Model;
  2. use Think\Model\RelationModel;
  3. class UserModel extends RelationModel{
  4. protected $_link = array(
  5. '关联1' => array(
  6. '关联属性1' => '定义',
  7. '关联属性N' => '定义',
  8. ),
  9. '关联2' => array(
  10. '关联属性1' => '定义',
  11. '关联属性N' => '定义',
  12. ),
  13. '关联3' => HAS_ONE, // 快捷定义
  14. ...
  15. );
  16. }

下面我们首先来分析下各个关联方式的定义:

HAS_ONE

HAS_ONE关联表示当前模型拥有一个子对象,例如,每个员工都有一个人事档案。我们可以建立一个用户模型UserModel,并且添加如下关联定义:

  1. namespace Home\Model;
  2. use Think\Model\RelationModel;
  3. class UserModel extends RelationModel{
  4. protected $_link = array(
  5. 'Profile'=> self::HAS_ONE,
  6. );
  7. }

上面是最简单的方式,表示其遵循了系统内置的数据库规范,完整的定义方式是:

  1. namespace Home\Model;
  2. use Think\Model\RelationModel;
  3. class UserModel extends RelationModel{
  4. protected $_link = array(
  5. 'Profile'=>array(
  6. 'mapping_type' => self::HAS_ONE,
  7. 'class_name' => 'Profile',
  8. // 定义更多的关联属性
  9. ……
  10. ),
  11. );
  12. }

关联HAS_ONE支持的关联属性有: mapping_type :关联类型

这个在HAS_ONE 关联里面必须使用HAS_ONE 常量定义。

class_name :要关联的模型类名

例如,class_name 定义为Profile的话则表示和另外的Profile模型类关联,这个Profile模型类是无需定义的,系统会自动定位到相关的数据表进行关联。

mapping_name :关联的映射名称,用于获取数据用

该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。如果mapping_name没有定义的话,会取class_name的定义作为mapping_name。如果class_name也没有定义,则以数组的索引作为mapping_name。

foreign_key : 关联的外键名称

外键的默认规则是当前数据对象名称_id,例如: UserModel对应的可能是表think_user (注意:think只是一个表前缀,可以随意配置) 那么think_user表的外键默认为 user_id,如果不是,就必须在定义关联的时候显式定义 foreign_key 。

condition : 关联条件

关联查询的时候会自动带上外键的值,如果有额外的查询条件,可以通过定义关联的condition属性。

mapping_fields : 关联要查询的字段

默认情况下,关联查询的关联数据是关联表的全部字段,如果只是需要查询个别字段,可以定义关联的mapping_fields属性。

as_fields :直接把关联的字段值映射成数据对象中的某个字段

这个特性是ONE_TO_ONE 关联特有的,可以直接把关联数据映射到数据对象中,而不是作为一个关联数据。当关联数据的字段名和当前数据对象的字段名称有冲突时,还可以使用映射定义。

BELONGS_TO

Belongs_to 关联表示当前模型从属于另外一个父对象,例如每个用户都属于一个部门。我们可以做如下关联定义。

  1. 'Dept' => self::BELONGS_TO

完整方式定义为:

  1. 'Dept' => array(
  2. 'mapping_type' => self::BELONGS_TO,
  3. 'class_name' => 'Dept',
  4. 'foreign_key' => 'userId',
  5. 'mapping_name' => 'dept',
  6. // 定义更多的关联属性
  7. ……
  8. ),

关联BELONGS_TO定义支持的关联属性有:

属性 描述
class_name 要关联的模型类名
mapping_name 关联的映射名称,用于获取数据用 该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。
foreign_key 关联的外键名称
mapping_fields 关联要查询的字段
condition 关联条件
parent_key 自引用关联的关联字段 默认为parent_id 自引用关联是一种比较特殊的关联,也就是关联表就是当前表。
as_fields 直接把关联的字段值映射成数据对象中的某个字段

HAS_MANY

HAS_MANY 关联表示当前模型拥有多个子对象,例如每个用户有多篇文章,我们可以这样来定义:

  1. 'Article' => self::HAS_MANY

完整定义方式为:

  1. 'Article' => array(
  2. 'mapping_type' => self::HAS_MANY,
  3. 'class_name' => 'Article',
  4. 'foreign_key' => 'userId',
  5. 'mapping_name' => 'articles',
  6. 'mapping_order' => 'create_time desc',
  7. // 定义更多的关联属性
  8. ……
  9. ),

关联HAS_MANY定义支持的关联属性有:

属性 描述
class_name 要关联的模型类名
mapping_name 关联的映射名称,用于获取数据用 该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。
foreign_key 关联的外键名称
parent_key 自引用关联的关联字段 默认为parent_id
condition 关联条件 关联查询的时候会自动带上外键的值,如果有额外的查询条件,可以通过定义关联的condition属性。
mapping_fields 关联要查询的字段 默认情况下,关联查询的关联数据是关联表的全部字段,如果只是需要查询个别字段,可以定义关联的mapping_fields属性。
mapping_limit 关联要返回的记录数目
mapping_order 关联查询的排序

外键的默认规则是当前数据对象名称_id,例如:UserModel对应的可能是表think_user (注意:think只是一个表前缀,可以随意配置)  那么think_user表的外键默认为 user_id,如果不是,就必须在定义关联的时候定义 foreign_key 。

MANY_TO_MANY

MANY_TO_MANY 关联表示当前模型可以属于多个对象,而父对象则可能包含有多个子对象,通常两者之间需要一个中间表类约束和关联。例如每个用户可以属于多个组,每个组可以有多个用户:

  1. 'Group' => self::MANY_TO_MANY

完整定义方式为:

  1. 'Group' => array(
  2. 'mapping_type' => self::MANY_TO_MANY,
  3. 'class_name' => 'Group',
  4. 'mapping_name' => 'groups',
  5. 'foreign_key' => 'userId',
  6. 'relation_foreign_key' => 'groupId',
  7. 'relation_table' => 'think_group_user' //此处应显式定义中间表名称,且不能使用C函数读取表前缀
  8. )

MANY_TO_MANY支持的关联属性定义有:

属性 描述
class_name 要关联的模型类名
mapping_name 关联的映射名称,用于获取数据用 该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。
foreign_key 关联的外键名称 外键的默认规则是当前数据对象名称_id
relation_foreign_key 关联表的外键名称 默认的关联表的外键名称是表名_id
mapping_limit 关联要返回的记录数目
mapping_order 关联查询的排序
relation_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属性。

3.2.2版本开始,relation_table定义支持简化写法,例如:

  1. 'relation_table'=>'__USER_GROUP__'

关联查询

由于性能问题,新版取消了自动关联查询机制,而统一使用relation方法进行关联操作,relation方法不但可以启用关联还可以控制局部关联操作,实现了关联操作一切尽在掌握之中。

  1. $User = D("User");
  2. $user = $User->relation(true)->find(1);

输出$user结果可能是类似于下面的数据:

  1. array(
  2. 'id' => 1,
  3. 'account' => 'ThinkPHP',
  4. 'password' => '123456',
  5. 'Profile' => array(
  6. 'email' => 'liu21st@gmail.com',
  7. 'nickname' => '流年',
  8. ),
  9. )

我们可以看到,用户的关联数据已经被映射到数据对象的属性里面了。其中Profile就是关联定义的mapping_name属性。

如果我们按照下面的方式定义了as_fields属性的话,

  1. protected $_link = array(
  2. 'Profile'=>array(
  3. 'mapping_type' => self::HAS_ONE,
  4. 'class_name' => 'Profile',
  5. 'foreign_key' => 'userId',
  6. 'as_fields' => 'email,nickname',
  7. ),
  8. );

查询的结果就变成了下面的结果

  1. array(
  2. 'id' => 1,
  3. 'account' => 'ThinkPHP',
  4. 'password' => 'name',
  5. 'email' => 'liu21st@gmail.com',
  6. 'nickname' => '流年',
  7. )

email和nickname两个字段已经作为user数据对象的字段来显示了。

如果关联数据的字段名和当前数据对象的字段有冲突的话,怎么解决呢?

我们可以用下面的方式来变化下定义:

  1. 'as_fields' => 'email,nickname:username',

表示关联表的nickname字段映射成当前数据对象的username字段。

默认会把所有定义的关联数据都查询出来,有时候我们并不希望这样,就可以给relation方法传入参数来控制要关联查询的。

  1. $User = D("User");
  2. $user = $User->relation('Profile')->find(1);

关联查询一样可以支持select方法,如果要查询多个数据,并同时获取相应的关联数据,可以改成:

  1. $User = D("User");
  2. $list = $User->relation(true)->Select();

如果希望在完成的查询基础之上 再进行关联数据的查询,可以使用

  1. $User = D("User");
  2. $user = $User->find(1);
  3. // 表示对当前查询的数据对象进行关联数据获取
  4. $profile = $User->relationGet("Profile");

事实上,除了当前的参考模型User外,其他的关联模型是不需要创建的。

关联操作

除了关联查询外,系统也支持关联数据的自动写入、更新和删除

关联写入

  1. $User = D("User");
  2. $data = array();
  3. $data["account"] = "ThinkPHP";
  4. $data["password"] = "123456";
  5. $data["Profile"] = array(
  6. 'email' =>'liu21st@gmail.com',
  7. 'nickname' =>'流年',
  8. );
  9. $result = $User->relation(true)->add($data);

这样就会自动写入关联的Profile数据。

同样,可以使用参数来控制要关联写入的数据:

  1. $result = $User->relation("Profile")->add($data);

当MANY_TO_MANY时,不建议使用关联插入。

关联更新

数据的关联更新和关联写入类似

  1. $User = D("User");
  2. $data["account"] = "ThinkPHP";
  3. $data["password"] = "123456";
  4. $data["Profile"] = array(
  5. 'email' =>'liu21st@gmail.com',
  6. 'nickname' =>'流年',
  7. );
  8. $result = $User-> relation(true)->where(array('id'=>3))->save($data);

Relation(true)会关联保存User模型定义的所有关联数据,如果只需要关联保存部分数据,可以使用:

  1. $result = $User->relation("Profile")->save($data);

这样就只会同时更新关联的Profile数据。

关联保存的规则:

HAS_ONE: 关联数据的更新直接赋值

HAS_MANY: 的关联数据如果传入主键的值 则表示更新 否则就表示新增

MANY_TO_MANY: 的数据更新是删除之前的数据后重新写入

关联删除

  1. //删除用户ID为3的记录的同时删除关联数据
  2. $result = $User->relation(true)->delete("3");
  3. // 如果只需要关联删除部分数据,可以使用
  4. $result = $User->relation("Profile")->delete("3");

thinkphp 关联关系的更多相关文章

  1. thinkphp框架中“关联操作”的完整定义详解

    在复杂的关联操作中,如果要给关联定义增加可选的属性,我们可以采用完整定义的方式. 完整定义的格式是: protected $_link = array(     '关联表名1'  =>  arr ...

  2. ThinkPHP 3.2.3 简单后台模块开发(二)RBAC

    RBAC(Role-Based Access Controll)基于角色的访问控制 在 ThinkPHP3.2.3 中 RBAC 类位于 /ThinkPHP/Library/Org/Util/Rbac ...

  3. ThinkPHP的Rbac权限控制

    RBAC(Role-Based Access Controll)基于角色的访问控制 在 ThinkPHP3.2.3 中 RBAC 类位于 /ThinkPHP/Library/Org/Util/Rbac ...

  4. ThinkPHP中RBAC权限带菜单栏显示和详细权限操作

    RBAC是什么,能解决什么难题? RBAC是Role-Based Access Control的首字母,译成中文即基于角色的权限访问控制,说白了也就是用户通过角色与权限进行关联[其架构灵感来源于操作系 ...

  5. ThinkPHP使用方法与心得

    ThinkPHP相信PHP程序员对它并不陌生,通过自己的学习在此发表个人看法,也为以后自己查找ThinkPHP方面的知识更加方便. 一.mvc及数据库CURD操作流程: 1.新建数据库:数据库名称:1 ...

  6. (thinkPHP)PHP常用函数大全

    usleep() 函数延迟代码执行若干微秒.unpack() 函数从二进制字符串对数据进行解包.uniqid() 函数基于以微秒计的当前时间,生成一个唯一的 ID.time_sleep_until() ...

  7. 制作类似ThinkPHP框架中的PATHINFO模式功能

    一.PATHINFO功能简述 搞PHP的都知道ThinkPHP是一个免费开源的轻量级PHP框架,虽说轻量但它的功能却很强大.这也是我接触学习的第一个框架.TP框架中的URL默认模式即是PathInfo ...

  8. 在 SAE 上部署 ThinkPHP 5.0 RC4

    缘起 SAE 和其他的平台有些不同,不能在服务器上运行 Composer 来安装各种包,必须把源码都提交上去.一般的做法,可能是直接把源码的所有文件复制到目录中,添加到版本库.不过,这样就失去了与上游 ...

  9. ThinkPHP+Smarty模板中截取包含中英文混合的字符串乱码的解决方案

    好几天没写博客了,其实有好多需要总结的,因为最近一直在忙着做项目,但是困惑了几天的Smarty模板中截取包含中英文混合的字符串乱码的问题,终于解决了,所以记录下来,需要的朋友看一下: 出现乱码的原因: ...

随机推荐

  1. Devstack单节点环境实战配置

    本文为minxihou的翻译文章,转载请注明出处Bob Hou: http://blog.csdn.net/minxihou JmilkFan:minxihou的技术博文方向是 算法&Open ...

  2. python读取Excel表格文件

    python读取Excel表格文件,例如获取这个文件的数据 python读取Excel表格文件,需要如下步骤: 1.安装Excel读取数据的库-----xlrd 直接pip install xlrd安 ...

  3. 天道神诀---DHCP服务(上篇)

    DHCP DHCP(Dynamic Host Configuration Protocol, 动态主机配置协议),被应用在局域网环境中,主要作用是集中的管理.分配IP地址,使网路环境中的主机动态的获得 ...

  4. EFCore学习记录笔记

    1:连接slqlocaldb数据库 (1)在CMD下可以输入sqllocaldb info 查看本机安装的所有的localdb实例 (2)数据库连接字符串为:“Server=(localdb)\\MS ...

  5. 【转】WebResource实现在自定义控件中内嵌JS文件

    在类库中的资源  其他项目中要使用 需要嵌入才行 参考文献:WebResource实现在自定义控件中内嵌JS文件 1. WebResource简介 ASP.NET(1.0/1.1)给我们提供了一个开发 ...

  6. .net 裁剪图片

    private void GetImg() { ) { return; } ]; string[] imgsize = Request["imgsize"].Split('& ...

  7. 数据库MySQL--修改数据表

    创建数据库::create database 数据库名: 如果数据不存在则创建,存在不创建:Create database if not exists 数据库名 ; 删除数据库::drop datab ...

  8. Android开发 string.xml资源添加参数

    挖坑:参考:https://www.cnblogs.com/leelugen/p/6685905.html

  9. HDU5377

    题意:给sum,m组询问,每组x,y求\(x^t=y\mod p,p|sum\),p是素数,求最小的t 题解:先处理sum的所有质因子p,求出p的原根rt,\(rt^a=x\mod p,rt^b=y\ ...

  10. 线段树离散化+区间更新——cf1179C好题

    绝对是很好的题 把问题转化成当第i个询问的答案是数值x时是否可行 要判断值x是否可行,只要再将问题转化成a数组里>=x的值数量是否严格大于b数组里的>=x的值 那么线段树叶子结点维护对于值 ...