递归前必须用is_array()和is_object()显式判断类型,区分数组、对象(需校验可遍历性)及标量值;传参用引用防止内存爆炸;深度超50层强制中断;不可控输入优先用迭代替代递归。

递归前必须用 is_array() 和 is_object() 显式判断类型
PHP 里数组和对象在递归中行为完全不同,但 empty()、count() 对对象返回 0 或报错,foreach 遍历对象会访问 public 属性——不加区分直接递归,轻则漏数据,重则触发 Fatal error: Cannot use object as array。类型判断不能靠经验或默认假设。
- 先用
is_array($data)判断是否为数组,再进递归分支 - 再用
is_object($data)判断是否为对象,且需额外确认是否可遍历(比如stdClass可,DateTime不行) - 对既不是数组也不是对象的值(字符串、数字、
null、布尔),直接返回或跳过,不递归 - 避免用
gettype($data) === 'array',它不如is_array()稳定;gettype()返回object时无法区分是否含属性
foreach 遍历对象前必须加 property_exists() 或反射校验
直接 foreach ($obj as $k => $v) 会把对象所有 public 属性当键值对展开,但若对象实现了 Iterator 或重载了 __get,行为不可控;更危险的是,某些内置类(如 PDOStatement)会抛出异常。
- 安全做法:对对象先用
is_object($data) && $data instanceof \stdClass快速过滤常见容器型对象 - 不确定类型时,用
get_object_vars($data)获取 public 属性数组,它自动跳过 protected/private 和方法 - 需要处理所有属性(含 protected)时,改用
(new \ReflectionObject($data))->getProperties(),但注意性能开销 - 遇到
Resource、Closure、Generator等不可序列化类型,应直接return null或抛出InvalidArgumentException
递归调用时传参必须用引用 &$data 防止大对象重复克隆
PHP 7+ 对象默认是引用语义,但递归函数参数默认按值传递——这意味着每次调用都会触发对象浅拷贝(复制属性指针,非内容),而数组仍是完整复制。对含大量数据的 stdClass 或自定义对象,几十层递归下来内存翻倍,Allowed memory size exhausted 就来了。
- 声明函数时明确写
function traverse(&$data, $depth = 0),让对象和数组都以引用传入 - 如果函数内部需要修改副本而不影响原始数据,入口处手动做一次
$data = clone $data;(仅对支持__clone()的对象有效) - 对不可克隆对象(如
PDO实例),提前用is_object($data) && !method_exists($data, '__clone')拦截并报错 - 别依赖
json_encode()做类型“清洗”——它会丢掉资源、闭包、资源句柄,且性能差
深度超过 50 层必须中断,否则 xdebug.max_nesting_level 会炸
线上环境 xdebug.max_nesting_level 通常设为 100 或更低,而树形结构(如菜单、权限节点)嵌套 30 层就可能触顶。等报错才处理,已经晚了——脚本直接 500,没日志,难复现。
- 递归函数必带
$depth参数,初始调用传0,每次递归+1 - 开头就加
if ($depth > 50) { throw new \RuntimeException('Recursion depth exceeded'); } - 不要用全局静态变量计数,它在并发请求下会串数据;深度必须由参数传递,保证隔离性
- 对 JSON 解析、XML 转数组这类输入不可控场景,优先用迭代栈替代递归,
while (!empty($stack)) { $item = array_pop($stack); ... }更稳
类型判断和深度控制不是“可选优化”,是递归操作的前置安检。漏掉任何一个,都可能让函数在某个特定数据上静默失败或突然崩溃,而问题只出现在生产环境的某条边缘数据流里。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











