PHP递归函数实现递归过滤数据的方法
在实际开发中,我们经常会遇到多层嵌套的数据结构,比如无限级分类、多级节点树等。这类数据的过滤逻辑往往和层级结构相关,普通循环很难优雅处理,这时候递归函数就能派上用场。递归函数通过自身调用自身的方式,逐层遍历嵌套数据,再结合过滤条件筛选需要的内容,就能实现灵活的递归过滤效果。
递归过滤的核心思路
实现递归过滤主要分三步:
- 先判断当前处理的数据是不是数组或对象,如果不是就直接按条件判断是否保留
- 如果是数组或对象,就遍历它的每一个元素,对每个元素递归调用过滤函数
- 最后把递归处理后的结果重新组合,返回符合要求的过滤后数据
基础版:递归过滤数组中的空值
我们先从简单的场景入手,实现一个递归过滤数组中所有空值(包括子数组里的空值)的函数。下面的代码会遍历多层嵌套的数组,把所有的空字符串、null、空数组都过滤掉。
<?php
/**
* 递归过滤数组中的空值
* @param array $data 待过滤的嵌套数组
* @return array 过滤后的数组
*/
function recursiveFilterEmpty(array $data): array
{
$result = [];
foreach ($data as $key => $value) {
// 如果当前值是数组,递归处理子数组
if (is_array($value)) {
$filteredSub = recursiveFilterEmpty($value);
// 子数组过滤后不为空才加入结果
if (!empty($filteredSub)) {
$result[$key] = $filteredSub;
}
} else {
// 过滤空值:排除null、空字符串、空数组(这里空数组已经在上面的判断处理了)
if ($value !== null && $value !== '') {
$result[$key] = $value;
}
}
}
return $result;
}
// 测试数据
$testData = [
'name' => '张三',
'age' => 25,
'hobby' => '',
'address' => null,
'children' => [
'son' => [
'name' => '张小三',
'age' => null,
'toy' => '汽车'
],
'daughter' => []
]
];
$filtered = recursiveFilterEmpty($testData);
print_r($filtered);
?>运行上面的代码,输出结果如下,可以看到所有的空字符串、null、空子数组都被过滤掉了:
Array
(
[name] => 张三
[age] => 25
[children] => Array
(
[son] => Array
(
[name] => 张小三
[toy] => 汽车
)
)
)进阶版:自定义条件递归过滤
实际业务中过滤条件往往不是固定的,我们可以把过滤条件封装成回调函数,让递归函数更通用。下面的例子实现了根据自定义条件递归过滤多维数组,你可以传入不同的回调函数来定义过滤规则。
<?php
/**
* 自定义条件的递归过滤函数
* @param array $data 待过滤的嵌套数组
* @param callable $callback 过滤回调函数,接受$value和$key两个参数,返回bool表示是否保留
* @return array 过滤后的数组
*/
function recursiveFilterCustom(array $data, callable $callback): array
{
$result = [];
foreach ($data as $key => $value) {
// 如果当前值是数组,先递归处理子数组
if (is_array($value)) {
$filteredSub = recursiveFilterCustom($value, $callback);
// 子数组不为空就保留
if (!empty($filteredSub)) {
$result[$key] = $filteredSub;
}
} else {
// 调用回调函数判断是否保留当前值
if ($callback($value, $key)) {
$result[$key] = $value;
}
}
}
return $result;
}
// 测试数据:员工信息列表,包含多层部门结构
$employeeData = [
'tech_dept' => [
'manager' => ['name' => '李四', 'age' => 35, 'salary' => 20000],
'members' => [
['name' => '王五', 'age' => 22, 'salary' => 8000],
['name' => '赵六', 'age' => 28, 'salary' => 12000],
]
],
'hr_dept' => [
'manager' => ['name' => '钱七', 'age' => 32, 'salary' => 18000],
'members' => [
['name' => '孙八', 'age' => 25, 'salary' => 9000],
]
]
];
// 过滤条件:只保留薪资大于等于10000的员工信息
$filterCondition = function ($value, $key) {
// 如果是薪资字段,判断是否大于等于10000
if ($key === 'salary') {
return $value >= 10000;
}
// 非薪资字段默认保留,交给上层逻辑处理
return true;
};
$filteredEmployee = recursiveFilterCustom($employeeData, $filterCondition);
print_r($filteredEmployee);
?>运行后你会发现,薪资低于10000的员工信息会被过滤掉,而部门和经理信息只要满足条件就会保留,子数组递归过滤的逻辑也正常生效。
注意事项
使用递归函数的时候要注意几个问题:
- 避免无限递归:如果数据结构里存在循环引用(比如A包含B,B又包含A),递归会一直执行直到耗尽内存,处理前最好先检查数据结构是否有循环引用
- 性能问题:递归层级过深的时候,函数调用栈会占用较多内存,如果数据层级超过几百层,建议改用迭代加栈的方式实现
- 参数传递:如果处理的是大数组,建议在递归的时候传递数组的引用,减少内存拷贝的开销,比如把
recursiveFilterEmpty(array $data)改成recursiveFilterEmpty(array &$data),但要注意引用传递可能会修改原数组,根据需求选择
上面的两个示例覆盖了大部分递归过滤的场景,你可以根据自己的业务需求调整过滤逻辑,比如过滤对象属性、过滤特定类型的元素等,核心思路都是递归遍历+条件判断+结果重组。