🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 关联模型 [上一页](77.html "上一页")[下一页](79.html "下一页") ### 关联关系 通常我们所说的关联关系包括下面三种: 一对一关联 :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(<br class="calibre5"></br>    '关联1'  =>  array(<br class="calibre5"></br>        '关联属性1' => '定义',<br class="calibre5"></br>        '关联属性N' => '定义',<br class="calibre5"></br>    ),<br class="calibre5"></br>    '关联2'  =>  array(<br class="calibre5"></br>        '关联属性1' => '定义',<br class="calibre5"></br>        '关联属性N' => '定义',<br class="calibre5"></br>    ),<br class="calibre5"></br>    '关联3'  =>  HAS_ONE, // 快捷定义<br class="calibre5"></br>    ...<br class="calibre5"></br>);`下面我们首先来分析下各个关联方式的定义: **HAS\_ONE** HAS_ONE关联表示当前模型拥有一个子对象,例如,每个员工都有一个人事档案。我们可以建立一个用户模型UserModel,并且添加如下关联定义:`class UserModel extends RelationModel{<br class="calibre5"></br>    protected $_link = array(<br class="calibre5"></br>       'Profile'=> HAS_ONE,<br class="calibre5"></br>    );<br class="calibre5"></br>}`上面是最简单的方式,表示其遵循了系统内置的数据库规范,完整的定义方式是:`class UserModel extends RelationModel{<br class="calibre5"></br>    protected $_link = array(<br class="calibre5"></br>            'Profile'=>array(<br class="calibre5"></br>            'mapping_type'    =>HAS_ONE,<br class="calibre5"></br>                 'class_name'    =>'Profile',<br class="calibre5"></br>                 // 定义更多的关联属性<br class="calibre5"></br>              ……<br class="calibre5"></br>             ),<br class="calibre5"></br>         );<br class="calibre5"></br>}`关联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 关联表示当前模型从属于另外一个父对象,例如每个用户都属于一个部门。我们可以做如下关联定义。` 'Dept'=> BELONGS_TO`完整方式定义为: `'Dept'=> array(  <br class="calibre5"></br>     'mapping_type'=>BELONGS_TO,<br class="calibre5"></br>          'class_name'=>'Dept',<br class="calibre5"></br>          'foreign_key'=>'userId',<br class="calibre5"></br>          'mapping_name'=>'dept',<br class="calibre5"></br>           // 定义更多的关联属性<br class="calibre5"></br>        ……<br class="calibre5"></br>),`关联BELONGS_TO定义支持的关联属性有:class\_name 要关联的模型类名mapping\_name 关联的映射名称,用于获取数据用 该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。foreign\_key 关联的外键名称mapping\_fields 关联要查询的字段condition 关联条件parent\_key 自引用关联的关联字段 默认为parent\_id 自引用关联是一种比较特殊的关联,也就是关联表就是当前表。as\_fields直接把关联的字段值映射成数据对象中的某个字段**HAS\_MANY** HAS_MANY 关联表示当前模型拥有多个子对象,例如每个用户有多篇文章,我们可以这样来定义:`'Article'=> HAS_MANY`完整定义方式为: `'Article'=> array(  <br class="calibre5"></br>    'mapping_type'=>HAS_MANY,<br class="calibre5"></br>                    'class_name'=>'Article',<br class="calibre5"></br>                    'foreign_key'=>'userId',<br class="calibre5"></br>                    'mapping_name'=>'articles',<br class="calibre5"></br>                    'mapping_order'=>'create_time desc',<br class="calibre5"></br>         // 定义更多的关联属性<br class="calibre5"></br>        ……<br class="calibre5"></br>       ),`关联HAS_MANY定义支持的关联属性有:class\_name要关联的模型类名mapping\_name 关联的映射名称,用于获取数据用 该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。foreign\_key 关联的外键名称 外键的默认规则是当前数据对象名称\_id,例如: UserModel对应的可能是表think\_user (注意:think只是一个表前缀,可以随意配置) 那么think\_user表的外键默认为 user\_id,如果不是,就必须在定义关联的时候定义 foreign\_key 。parent\_key 自引用关联的关联字段 默认为parent\_id condition 关联条件 关联查询的时候会自动带上外键的值,如果有额外的查询条件,可以通过定义关联的condition属性。mapping\_fields 关联要查询的字段 默认情况下,关联查询的关联数据是关联表的全部字段,如果只是需要查询个别字段,可以定义关联的mapping\_fields属性。mapping\_limit 关联要返回的记录数目mapping\_order 关联查询的排序**MANY\_TO\_MANY** MANY_TO_MANY 关联表示当前模型可以属于多个对象,而父对象则可能包含有多个子对象,通常两者之间需要一个中间表类约束和关联。例如每个用户可以属于多个组,每个组可以有多个用户: `'Group'=>MANY_TO_MANY`完整定义方式为: `array('mapping_type'=>MANY_TO_MANY,<br class="calibre5"></br>'class_name'=>'Group',<br class="calibre5"></br>'mapping_name'=>'groups',<br class="calibre5"></br>'foreign_key'=>'userId',<br class="calibre5"></br>'relation_foreign_key'=>'goupId',<br class="calibre5"></br>'relation_table'=>'think_gourpUser'<br class="calibre5"></br>)`MANY_TO_MANY支持的关联属性定义有:class\_name要关联的模型类名mapping\_name 关联的映射名称,用于获取数据用 该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。foreign\_key 关联的外键名称 外键的默认规则是当前数据对象名称\_idrelation\_foreign\_key 关联表的外键名称 默认的关联表的外键名称是表名\_idmapping\_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属性。### 关联查询 由于性能问题,新版取消了自动关联查询机制,而统一使用relation方法进行关联操作,relation方法不但可以启用关联还可以控制局部关联操作,实现了关联操作一切尽在掌握之中。`$User = D("User");<br class="calibre5"></br>$user =    $User->relation(true)->find(1);`输出$user结果可能是类似于下面的数据:`array(<br class="calibre5"></br>'id'        =>    1,<br class="calibre5"></br>'account'    =>    'ThinkPHP',<br class="calibre5"></br>'password'    =>    '123456',<br class="calibre5"></br>'Profile'    => array(<br class="calibre5"></br>'email'        =>'liu21st@gmail.com',<br class="calibre5"></br>'nickname'    =>'流年',<br class="calibre5"></br>   ),<br class="calibre5"></br> )`我们可以看到,用户的关联数据已经被映射到数据对象的属性里面了。其中Profile就是关联定义的mapping_name属性。 如果我们按照下面的方式定义了as_fields属性的话,`    protected $_link = array(<br class="calibre5"></br>        'profile'=>array(<br class="calibre5"></br>    'mapping_type'    =>HAS_ONE,<br class="calibre5"></br>                    'class_name'    =>'Profile',<br class="calibre5"></br>    'foreign_key'=>'userId',<br class="calibre5"></br>    'as_fields'=>'email,nickname',<br class="calibre5"></br>         ),<br class="calibre5"></br>);`查询的结果就变成了下面的结果`array(<br class="calibre5"></br>'id'        =>    1,<br class="calibre5"></br>'account'    =>    'ThinkPHP',<br class="calibre5"></br>'password'    =>    'name',<br class="calibre5"></br>'email'        =>'liu21st@gmail.com',<br class="calibre5"></br>'nickname'    =>'流年',<br class="calibre5"></br> )`email和nickname两个字段已经作为user数据对象的字段来显示了。 如果关联数据的字段名和当前数据对象的字段有冲突的话,怎么解决呢? 我们可以用下面的方式来变化下定义: 'as_fields'=>'email,nickname:username', 表示关联表的nickname字段映射成当前数据对象的username字段。 默认会把所有定义的关联数据都查询出来,有时候我们并不希望这样,就可以给relation方法传入参数来控制要关联查询的。`$User = D("User");<br class="calibre5"></br>$user =    $User->relation('Profile')->find(1);`关联查询一样可以支持select方法,如果要查询多个数据,并同时获取相应的关联数据,可以改成:`$User = D("User");<br class="calibre5"></br>$list =    $User->relation(true)->Select();`如果希望在完成的查询基础之上 再进行关联数据的查询,可以使用`$User = D("User");<br class="calibre5"></br>$user = $User->find(1);<br class="calibre5"></br>// 表示对当前查询的数据对象进行关联数据获取<br class="calibre5"></br>$profile = $User->relationGet("Profile");`事实上,除了当前的参考模型User外,其他的关联模型是不需要创建的。### 关联操作 除了关联查询外,系统也支持关联数据的自动写入、更新和删除 **关联写入**`$User = D("User");<br class="calibre5"></br>$data = array();<br class="calibre5"></br>$data["account"]    = "ThinkPHP";<br class="calibre5"></br>$data["password"] = "123456";<br class="calibre5"></br>$data["Profile"]    = array(<br class="calibre5"></br>    'email'    =>'liu21st@gmail.com',<br class="calibre5"></br>    'nickname'    =>'流年',<br class="calibre5"></br>);<br class="calibre5"></br>$result =$User->relation(true)->add($data);`这样就会自动写入关联的Profile数据。 同样,可以使用参数来控制要关联写入的数据:`$result = $User->relation("Profile")->add($data);`**关联更新** 数据的关联更新和关联写入类似`$User = D("User");<br class="calibre5"></br>$data["account"]    = "ThinkPHP";<br class="calibre5"></br>$data["password"] = "123456";<br class="calibre5"></br>$data["Profile"]    = array(<br class="calibre5"></br>    'email'    =>'liu21st@gmail.com',<br class="calibre5"></br>    'nickname'    =>'流年',<br class="calibre5"></br>);<br class="calibre5"></br>$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");` [上一页](77.html "上一页")[下一页](79.html "下一页")