php无限分类也可以实现网站导航的效果,例如1688的官网头部导航:
测试数据:
const arr = [
['id' => 1, 'pid' => 0, 'city' => '江西'],
['id' => 2, 'pid' => 0, 'city' => '广东'],
['id' => 3, 'pid' => 1, 'city' => '宜春'],
['id' => 4, 'pid' => 3, 'city' => '丰城'],
['id' => 5, 'pid' => 4, 'city' => '尚庄'],
['id' => 6, 'pid' => 2, 'city' => '深圳'],
['id' => 7, 'pid' => 6, 'city' => '翻身'],
#以下是追加的同级元素数据,用来试验论证,也是增强干扰性
['id' => 8, 'pid' => 1, 'city' => '南昌'],
['id' => 9, 'pid' => 8, 'city' => '高新区'],
];
思路分析:
传入一个顶级pid ,只有当pid为0时,才是顶级,故pid的值为0。
根据pid找到当前类别的数据,得到当前类别的id,以此当作当前类别的子类pid传入。
继续重复刚刚上面这个行为,直到找不到子类为止,则跳出当前类别的查找,继续下一个类别的查找。
也就是:传入pid = 0 ,只有id为1和id为2的江西和广东符合条件。
1.先处理江西,再找到宜春和南昌,
2.先处理宜春,再根据宜春找到丰城,再根据丰城找到尚庄,再根据尚庄去找发现没有子级,跳出宜春;
3.宜春处理完毕后,来处理与之同级的南昌;根据南昌找到高新区,根据高新区去找发现没有子级,跳出南昌来到江西。
4.此时发现江西的子类宜春和南昌处理完毕,跳出来到最顶级的广东。
4.根据广东,找到深圳,再根据深圳找到翻身,根据翻身去找发现没有子级,再跳出,发现所有数据处理完毕,程序结束。
根据上面的思路得知,这是一个可以无限调用自身的方法,也就是递归来处理。
代码实践:
下列将逐渐迭代版本,以帮助理解。
唠叨一下:
网上的例子大多都是给出一个最终版叫人去看,不过我想如果你能看到我这篇文章,多半也和我一样看了也不明所以,所以还是自己写一个熟悉的层级关系数组来实践为好。
首先要明白的思路是:声明一个数组,用这个数组去存储每次得到的分类返回值,再将这个分类返回值以键名child在它的父级中以元素的形式插入该数据,如此往复,得到一个完整的层级关系大数组。
版本一:
public function test(array $data , $pid = 0){
$array = [];
foreach ($data as $key => $value) {
//找到当前分类的下一级(子级)
if($value['pid'] == $pid){
$array[$value['id']] = $value;
$array[$value['id']]['child'] = $this->test($data,$value['id']);
}
}
return $array;
}
效果如下:
这一版需理解大概思路是:
1.传入了顶级数据(我此刻就是顶级数据,也就爷爷!) , 然后声明了$array这个数组存储每一个孙子的数据并返回
2.使用$array去存储当前分类子级(我儿子)的数据
3.(我)儿子可能不止一个,可能有很多个,所以需要使用每个儿子的id作为键名来存储每个儿子的值,保证每个儿子的数据不会乱套,即$array[$value['id']] = $value;
如果使用$pid或者$key去作键名存储,要意识到:这两玩意是会重复的不具备唯一性,这样就导致每个儿子的数据乱了,可能后面的儿子和前面的儿子键名都一样,后者把前者覆盖了,这就尴尬了!所以这两玩意不能作为键名存储儿子的数据。
4.那我有了儿子的数据,我也得有儿子的儿子(孙子)的数据哇。那孙子的数据怎么来呢?
这简单,把儿子的id继续往自身函数里一丢再返回出来不就得到了吗?于是此时递归产生了....
5.通过递归,我找到了孙子的数据,并且也把孙子的数据返回了出来;那我得把孙子的数据丢到儿子的数据里吧?孙子的名字在儿子那叫什么好呢?有了,就叫child吧!哈哈孩子!
所以就出现了$array[$value['id']]['child'] = $this->test($data,$value['id']);
6.诶?神奇的一幕发生了,我作为爷爷,拿到了儿子的数据,也通过递归拿到了孙子的数据。但是我的孙子还有儿子和孙子哇,所以在把儿子的id继续往自身一丢的时候,我的(每个)儿子就成了曾孙们的爷爷!再如此往复...我的曾孙们也成为了爷爷!
这样就出现了一个分类关系:我就是祖宗,我的儿子们是二代,我的孙子们是三代;
但是我的儿子和孙子们要想得到它们子辈数据,就得像我一样成为爷爷才行,也就是说:每一个分类都应该以child为键名存储它的子级(儿子)。
在第一次传入顶级pid=0,得到$array(子级) , $array[$value['id']]['child'](孙子级)
递归开始:
第二次传入:儿子id=1 ,得到$array(曾孙), $array[$value['id']]['child'](曾曾孙)
无限递归中....
第n次传入:没有子级了,好了,我已经是家族里目前最小的孩子了(没有比我更小的了!),我本身的数据是$array,该把$array塞到我的父级以child为键名的元素中去了...欸?我的父亲还有父亲,我的父亲要把它的数据给他的父亲(我的爷爷)...欸?我的爷爷还有父亲,我爷爷的父亲也该把数据给他的父亲(我的曾曾曾祖父)...如此往复...这样逐渐自下往上提交数据,最顶级的人就拿到了整个家族的数据(整个分类)了!
7.如果你觉得上面的关系太过复杂,可以看下列:
从1到100
1 2 3 4 5 6 7 8 9 10 ... 100
我此刻是1,我的下一位数是2,我的下下位是3,再多我就不知道了。
也就是1['child'] = 2 , 2['child'] = 3
既然我不知道,那我就让我的儿子来呗,也就是让2来。
此刻我是2,我的下一位是3,我的下下位是4,再多我就不知道了。
也就是2['child'] = 3 , 3['child'] = 4
既然我不知道,那我就让我的儿子来呗,也就是让3来。
........
直到这样:
此刻我是99,我的下一位是100,我的下下位没有了!
也就是99['child'] = 100 , 100['child'] = ‘’
啊,原来在我们家100是尽头了啊...那我要开始汇报了
99['child'] =[
100['child'] = ‘’
]
98['child'] =[
99['child'] = [
100['child'] = ‘’
]
]
如此往复:
最终得到:
1['child'] =[
2['child'] = [
3['child'] = [
..........
100['child'] = ‘’
]
]
]
与上面的版本一也是同理,传入顶级id,找到最小的分类,再从最小的分类开始自下而上逐级塞入child值,最后顶级id所在的数组就拿到了整个大数组数据。
版本二:
public function test(array $data , $pid = 0){
$array = [];
foreach ($data as $key => $value) {
if($value['pid'] == $pid){
$value['child'] = $this->test($data,$value['id']);
$array[]= $value;
}
}
return $array;
}
仔细查看版本一发现:最终都是将$value的数组塞入$array中,那只要得到当前分类以child为键名的子级数据再塞入$array中就好了呀,于是出现了
$value['child'] = $this->test($data,$value['id']);
$array[]= $value;
效果如下:
小影响:与第一版不同的是,键名元素都变成了0
版本三:
在版本二的基础上,加入了if判断是否有子级分类的数据,如果有才以child为键名,递归值为键值。
public function test(array $data , $pid = 0){
$array = [];
foreach ($data as $key => $value) {
if($value['pid'] == $pid){
$childData = $this->test($data,$value['id']); //得到孙子的数据
if(!empty($childData)){ //孙子的数据不为空,才把孙子的数据以child为键塞入儿子数组中
$value['child'] = $childData;
}
$array[]= $value;
}
}
return $array;
}
页面渲染完整代码:
最后因为网站导航一般都是双层关系,也就是只需要两个循环即可完成。一个循环一级分类,另一个循环子级分类,最后拼接html,返回前端渲染即可。
<?php
/**
* Created by phpStrom
* User: Anbin
* Date: 2022/8/31
* Time: 16:48
*/
class Nav
{
//测试数据
const arr = [
['id' => 1, 'pid' => 0, 'city' => '江西'],
['id' => 2, 'pid' => 0, 'city' => '广东'],
['id' => 3, 'pid' => 1, 'city' => '宜春'],
['id' => 4, 'pid' => 3, 'city' => '丰城'],
['id' => 5, 'pid' => 4, 'city' => '尚庄'],
['id' => 6, 'pid' => 2, 'city' => '深圳'],
['id' => 7, 'pid' => 6, 'city' => '翻身'],
#以下是追加的同级元素数据,用来试验论证,也是增强干扰性
['id' => 8, 'pid' => 1, 'city' => '南昌'],
['id' => 9, 'pid' => 8, 'city' => '高新区'],
];
/**
* 传入数据得到导航栏html
* @param array $data
* @return string
*/
public function getNav(array $data):string
{
$str = '';
foreach ($data as $value) {
$str .= '<h2>' . $value['city'] . '</h2>'; //导航一级菜单
$str .= '<ul>';
if (isset($value['child'])) { //二级菜单(若存在)
foreach ($value['child'] as $k => $item) {
$str .= '<li>' . $item['city'] . '</li>';
}
}
$str .= '</ul>';
}
return $str;
}
/**
* 无限分类得到子孙关系大数组
* @param array $data
* @param int $pid
* @return array
*/
public function test(array $data, $pid = 0)
{
$array = [];
foreach ($data as $value) {
if ($value['pid'] == $pid) {
$childData = $this->test($data, $value['id']); //得到孙子的数据
if (!empty($childData)) { //孙子的数据不为空,才把孙子的数据以child为键塞入儿子数组中
$value['child'] = $childData;
}
$array[] = $value;
}
}
return $array;
}
/**
* 查看页面效果
* @return string
*/
public function index()
{
$res = $this->test(self::arr);
if (!empty($res)) {
return $this->getNav($res);
}
}
}
$a = new Nav();
echo $a->index();