业务系统中现有:学生表、班级表、学校表。
现需要查询学生表的同时关联班级表和学校表以查询某个学生属于哪个班级和哪个学校。
学生表 student
(只列出部分字段):
字段名 | 含义 |
---|---|
id | 主键 |
room_id | 关联班级表外键 |
name | 学生姓名 |
sex | 学生性别 |
address | 家庭住址 |
班级表 room
:
字段名 | 含义 |
---|---|
id | 主键 |
school_id | 关联学校表外键 |
name | 班级名称 |
学校表 school
:
字段名 | 含义 |
---|---|
id | 主键 |
name | 学校名称 |
graph LR
A(学生表) -- 关联 --> B(班级表)
B(班级表) -- 关联 --> C(学校表)
A(学生表)-. 无直接关联关系 .->C(学校表)
先来看一下主从表的定义:
主表: 被作为外键引用的表。
从表: 有外键引用的表。
也就是说,在两个表的关系中,有外键的表叫做从表,只有主键而没有外键的表叫做主表。
那么,学生表和班级表的关系:学生表是从表(存在外键room_id),班级表是主表。
同样,班级表和学校表的关系:班级表是从表(存在外键school_id),学校表是主表。
在 ThinkPHP5
中给出了两种一对一关联的方法:hasOne
和 belongsTo
那么,什么时候用hasOne
什么时候用 belongsTo
呢?
其实很简单,通过表的主从关系就可以得出(因为表和模型是相对应的):
1、在从表中建立关联关系用belongsTo
,可以理解为从表有从属于(主表)的意思。
2、在主表中建立关联关系用hasOne
,可以理解为从主表去对应从表的数据时只会对应单个。
理清了以上的关系之后,我们就可以愉快的写代码了,先来看学生模型中(StudentModel
)的关联方法定义:
public function room()
{
return $this->belongsTo('RoomModel', 'room_id', 'id');
}
很简单,学生从属于班级,用belongsTo
方法,第一个参数为要关联的模型名(这里去关联班级模型),第二个参数为当前模型(StudentModel
)或者说表(student
)的关联外键(room_id
),第三个参数为被关联模型(RoomModel
)或者说表(room
)的主键(id
)。
这样学生关联班级就完成了,接下来再来看班级关联学校(RoomModel
)中的关联方法定义:
public function school()
{
return $this->belongsTo('SchoolModel', 'school_id', 'id');
}
也很简单,班级从属于学校,同样用belongsTo
方法。
至此,关联方法定义好了,那怎么从控制器中去调用查询呢,请往下看:
我们在学生(Student
)控制器中去调用查询(因为我们主要的目的就是查询学生表):
$data = StudentModel::with(['room' => ['school']])->select()->toArray();
首先 with()
方法接受一个数组,可以理解为当前模型(StudentModel
)先去关联班级模型(RoomModel
),然后班级模型又去关联学校模型(SchoolModel
)。当然关联查询时写的是在相应模型中定义的关联方法(room
和school
),而不是模型名。
我们看打印查询出来的数据结构(只列出一条):
array(5) {
[0] => array(10) {
["id"] => int(1)
["image"] => string(6) "暂无"
["name"] => string(9) "张三"
["sex"] => string(3) "男"
["idcard"] => string(18) "37*********1X"
["address"] => string(27) "山东省济南市"
["room_id"] => int(5)
["status"] => string(3) "否"
["create_time"] => string(19) "2020-04-14 11:56:04"
["room"] => array(4) {
["id"] => int(5)
["name"] => string(10) "Java一班"
["school_id"] => int(1)
["school"] => array(2) {
["id"] => int(1)
["name"] => string(37) "山东大学"
}
}
}
}
可以看到在学生数据的数组里面多了一个room
元素,而在班级数据的数组里面又多了一个school
元素,这就是模型关联后查询出来的数据结构。
如果想在模板文件中循环输出可以这么写:
<td>{$vo.name}</td>
<td>{$vo.sex}</td>
<td>{$vo.idcard}</td>
<td>{$vo.address}</td>
<!-- 显示学校名称 -->
<td>{$vo.room.school.name}</td>
<!-- 显示班级名称 -->
<td>{$vo.room.name}</td>
如果想把班级的数据和学校的数据并列到学生数组怎么办呢?
我们可以用bind()
方法将子模型的属性绑定到父模型下,那么修改模型中关联方法如下:
public function school()
{
return $this->belongsTo('SchoolModel', 'school_id', 'id')
->bind(['school_name2' => 'name']);
}
public function room()
{
return $this->belongsTo('RoomModel', 'room_id', 'id')
->bind(['room_name' => 'name', 'school_name' => 'school_name2']);
}
解释一下:
因为我们三个表中都有name
字段,所以在绑定过去的时候,为了不和学生表中的name
重复,我们需要换一个名字(起别名)再绑定过去。
因为是绑定到父模型,所以我们首先要把SchoolModel
中的属性绑定到RoomModel
,再把RoomModel
中的属性绑定到StudentModel
,层层往前递进。
1、首先看school()
方法里面,我们把学校名称name
字段起别名为school_name2
(注意数组里面写法是由后往前,键是新字段名,值是旧字段名),然后绑定到其父模型(RoomModel
)。
2、再看room()
方法里面,我们不仅需要把班级名称name
字段重命名绑定到StudentModel
,还要把其子模型绑定过来的school_name2
字段也重命名绑定到StudentModel
。
如此,绑定也就完成了,再来看下打印出的数据结构(只列出一条):
array(5) {
[0] => array(11) {
["id"] => int(1)
["image"] => string(6) "暂无"
["name"] => string(9) "张三"
["sex"] => string(3) "男"
["idcard"] => string(18) "37*********1X"
["address"] => string(27) "山东省济南市"
["room_id"] => int(5)
["status"] => string(3) "否"
["create_time"] => string(19) "2020-04-14 11:56:04"
// 这是绑定过来的字段
["room_name"] => string(10) "Java一班"
["school_name"] => string(37) "山东大学"
}
}
同样的道理,在模板文件循环输出时就可以这样写了:
<td>{$vo.school_name}</td>
<td>{$vo.room_name}</td>
本文结束。