需求
查询这样的有父子层次关系的菜单数据,例如id=2的数据有子数据,id=3,4,5,6,16,因为后面这些数据的parent_id都是2
比较笨的办法
先查询每一条数据,然后再遍历查询parent_id = 每一条数据的id,最后数组拼接。这种方法不仅笨,查询次数还多,完全没必要。
模型自关联
laravel自带的ORM是个神器,针对这种有关系的数据,完全可以使用关系模型,既简单又实用,由于这里只有一个数据表,关系也存在于同一张表,所以可以直接使用自关联,将两个关系定义在同一个Model里面:1
2
3
4
5
6
7
8......
public function parent() {
return $this->hasOne($this, 'id', 'parent_id');
}
public function children() {
return $this->hasMany($this, 'parent_id', 'id');
}
关系定义里面的参数也可以这样写:1
2
3
4
5
6
7
8......
public function parent() {
return $this->hasOne(get_class($this), $this->getKeyName(), 'parent_id');
}
public function children() {
return $this->hasMany(get_class($this), 'parent_id', $this->getKeyName());
}
查询包含子菜单的数据
1 | return Admin_menu::with(['children' => function($query){ |
同理查询包含父菜单的数据,将with参数’children’换成’parent’即可。
关联键取别名查询不出数据
1 | return Admin_menu::with(['children' => function($query){ |
分析sql语句
1 | \DB::connection()->enableQueryLog(); // 开启查询日志 |
打印sql语句,不取别名的情况下,第二条查询,bindings应该是有值的数组,也就是 where in ()是有值可以查询的;给id取了别名后会发现,binding变成null,where in(null)也就查不到数据。
这里我的猜想是,where in (array)这里的array是依赖主键的名称的,在关联查询的时候,已经定义了id = [3,4,5,6…],但是我们最后给id取了别名,变成MaindId,所以找不到名为id的数组。
如果真是这样,我们试着再给它加上id,让它能够找到名为id的数组
1 | \DB::connection()->enableQueryLog(); // 开启查询日志 |
这里可以看到bingdings已经不再是null了。
总结
虽然以上取别名问题所在只是我的猜想,但大致可以得出结论:依赖关联主键localKey的查询,不能缺少相应的字段,也就是说select应该包含对应localKey,如果要取别名应该额外添加,形如:1
select('id', 'id as MainId', 'title', 'parent_id', 'parent_id as extraId')
附加另外一种处理数据格式方法
另外写一个私有的格式处理方法transformer,取别名就交给这个方法来转换1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public function test() {
$menus = Admin_menu::with(['parent' => function($query){
$query->select('id', 'title', 'parent_id');
}])
->select('id', 'title', 'parent_id')
->get();
return $this->transformer($menus);
}
protected function transformer($items) {
$data = [];
foreach ($items ?? [] as $item) {
$data[] = [
'mainId' => $item->id,
'title' => $item->title,
'parent_id' => $item->parent_id,
'children' => $item->children,
];
}
return $data;
}