方法一:使用迭代方式(推荐)
1、先修改模型
在原有方法的基础上,先添加当前分类的名称,然后再递归或迭代查找上级分类。
<?php
namespace app\model;use think\Model;class LaCate extends Model
{protected $table = 'la_cate';/*** 获取当前分类及其所有上级分类名称,用 "-" 分隔** @param int $cateId 当前分类的ID* @return string 分类名称,用 "-" 分隔(包含当前分类)*/public function getParentNames($cateId){$names = [];$currentId = $cateId;// 1. 先查询当前分类$currentCate = self::find($currentId);if (!$currentCate) {// 如果找不到当前分类,返回空字符串return '';}// 2. 先把当前分类名称加入数组$names[] = $currentCate->name;// 3. 获取父级 ID$parentId = $currentCate->pid;// 4. 开始迭代查找上级分类while ($parentId) {// 查询父级分类$parentCate = self::find($parentId);if (!$parentCate) {// 如果找不到父级分类,退出循环break;}// 将父级分类名称添加到数组$names[] = $parentCate->name;// 更新 parentId 为父级分类的 pid$parentId = $parentCate->pid;}// 5. 使用 "-" 拼接所有分类名称(顺序已经是当前分类 + 上级分类)return implode('-', $names);}
}
关键点:
- 先查询当前分类,并把它的名称加入 $names 数组。
- 再迭代查找上级分类,把父级分类名称依次加入数组。
- 最后用 implode(‘-’,$names) 拼接,顺序已经是 当前分类 - 父级分类 - 祖父级分类…。
方法二:使用递归方式
如果你更喜欢递归,也可以这样实现:
方法二:使用递归方式
<?php
namespace app\model;use think\Model;class LaCate extends Model
{protected $table = 'la_cate';/*** 获取当前分类及其所有上级分类名称,用 "-" 分隔** @param int $cateId 当前分类的ID* @return string 分类名称,用 "-" 分隔(包含当前分类)*/public function getParentNames($cateId){$names = [];$this->getCategoryNamesRecursive($cateId, $names);// 递归是先子后父,但我们在递归前已经添加了当前分类,所以顺序正确return implode('-', $names);}/*** 递归获取上级分类名称(不包含当前分类)** @param int $cateId 当前分类的ID* @param array &$names 用于存储名称的数组(引用传递)*/protected function getCategoryNamesRecursive($cateId, &$names){// 获取当前分类$currentCate = self::find($cateId);if (!$currentCate) {// 如果找不到当前分类,返回return;}$parentId = $currentCate->pid;if ($parentId == 0 || $parentId == null) {// 已经到达根分类,返回return;}// 查询父级分类$parentCate = self::find($parentId);if ($parentCate) {// 将父级分类名称添加到数组$names[] = $parentCate->name;// 递归查找父级的上级$this->getCategoryNamesRecursive($parentId, $names);}}
}
但这种方法需要调整,因为递归是先子后父,而我们希望先当前分类再上级分类。
所以更推荐迭代方式,因为它更直观且性能更好。
2、控制器调用
<?php
namespace app\controller;use app\model\LaCate;
use think\facade\Request;class LaCateController
{/*** 获取当前分类及其所有上级分类名称** @return \think\Response*/public function getParentNames(){// 获取请求参数中的 cateId$cateId = Request::param('cateId/d', 0);if ($cateId <= 0) {return json(['error' => '无效的分类ID'], 400);}// 实例化 LaCate 模型$laCate = new LaCate();// 调用方法获取分类名称(包含当前分类)$categoryNames = $laCate->getParentNames($cateId);// 返回结果return json(['category_names' => $categoryNames]);}
}
3、前端调用实现
假设 la_cate 表数据如下:
id | pid | name |
1 | 0 | 电子产品 |
2 | 1 | 手机 |
3 | 2 | 智能手机 |
4 | 1 | 电脑 |
5 | 4 | 笔记本 |
- 测试 1:id=5(笔记本)
- 当前分类:笔记本
- 父级分类:电脑 → 电子产品
- 返回结果:笔记本-电脑-电子产品
访问 URL:
http://yourdomain.com/la-cate/parent-names?cateId=5
返回 JSON:
json
{"category_names": "笔记本-电脑-电子产品"
}
测试 2:id=3(智能手机)
- 当前分类:智能手机
- 父级分类:手机 → 电子产品
- 返回结果:智能手机-手机-电子产品
访问 URL:
http://yourdomain.com/la-cate/parent-names?cateId=3
返回 JSON:
json
{"category_names": "智能手机-手机-电子产品"
}
tips
如果希望包含当前分类,只需在查询上级分类之前,先获取当前分类的名称,再迭代查找上级分类。
推荐使用迭代方式,因为它更高效且避免了递归可能导致的栈溢出问题。
最终返回的字符串格式:当前分类 - 父级分类 - 祖父级分类…。
在处理大量数据时,PHP的迭代和递归哪种更高效?
在处理大量数据(尤其是无限分类或层级较深的数据结构)时,PHP 的迭代(Iteration)通常比递归(Recursion)更高效,主要原因如下:
- 性能对比
对比项迭代(Iteration)递归(Recursion)内存占用较低(仅维护循环变量)较高(每次调用都会占用栈帧)执行速度更快(无函数调用开销)较慢(频繁函数调用)最大深度限制无(仅受内存限制)有(PHP 默认递归深度约 100-1000 层)代码复杂度稍复杂(需手动管理状态)更简单(逻辑直观)
- 为什么迭代更高效?
(1) 无函数调用开销
递归:每次调用自身都会产生新的栈帧(Stack Frame),存储局部变量、返回地址等,消耗更多内存和 CPU。
迭代:仅用 while/for 循环,无额外函数调用,性能更高。
(2) 无递归深度限制
PHP 默认递归深度限制(xdebug.max_nesting_level 或 max_execution_time 可能触发):
默认约 100-1000 层(取决于 PHP 版本和配置)。
超过限制会抛出 Fatal error: Maximum function nesting level reached。
迭代:仅受内存限制,可处理更深的层级(如百万级数据)。
(3) 更适合大数据量
递归在数据量大时可能导致:
栈溢出(Stack Overflow)(PHP 进程崩溃)。
性能急剧下降(函数调用开销累积)。
迭代则稳定可控,适合生产环境。
- 实际测试对比
- 假设查询一个 1000 层 的无限分类结构:
- 方式执行时间(示例)内存占用(示例)递归500ms(可能超时)50MB(栈帧累积)迭代50ms5MB
- (具体数值取决于服务器配置,但迭代通常快 10 倍以上)
- 如何选择?
场景推荐方式层级较浅(<100 层)递归(代码更简洁)层级深或数据量大迭代(性能更稳定)需要严格避免栈溢出迭代(100% 安全)
- 优化递归的方法(如必须用递归)
如果因业务逻辑必须用递归,可通过以下方式优化:
(1) 尾递归优化(PHP 不支持,但可模拟)
PHP 默认不支持尾递归优化(TCO),但可通过改写逻辑减少栈帧占用。
例如:用循环+栈数组模拟递归(类似迭代)。
(2) 增加递归深度限制检查
if ($depth > 1000) {
throw new Exception(“递归深度过大!”);
}
(3) 改用迭代
终极解决方案:直接改用 while 循环,彻底避免递归问题。
- 最终结论
优先使用迭代:处理无限分类、树形结构等大数据量场景时,迭代是更安全、高效的选择。
递归仅用于简单逻辑:如目录遍历(层级已知且较浅)等小规模数据。
附:迭代 vs 递归代码示例(无限分类查询)
迭代方式(推荐)
public function getParentNames($cateId) {$names = [];$currentId = $cateId;// 1. 先查当前分类$currentCate = self::find($currentId);if (!$currentCate) return '';$names[] = $currentCate->name;// 2. 迭代查父级while ($parentId = $currentCate->pid) {$parentCate = self::find($parentId);if (!$parentCate) break;$names[] = $parentCate->name;$currentCate = $parentCate;}return implode('-', $names);
}
递归方式(不推荐大数据量)
public function getParentNames($cateId) {$currentCate = self::find($cateId);if (!$currentCate) return '';$names = [];$this->collectParentNames($currentCate, $names);array_unshift($names, $currentCate->name); // 添加当前分类return implode('-', $names);
}protected function collectParentNames($cate, &$names) {if ($cate->pid == 0) return;$parent = self::find($cate->pid);if ($parent) {$names[] = $parent->name;$this->collectParentNames($parent, $names);}
}
显然,迭代方式更简洁高效! 🚀