由于这仅在其中一个示例的输出脚注中提到,我认为应该明确说明
* 此函数仅访问叶节点 *
也就是说,如果您有一个包含子数组的子数组的数组树,则只有树叶上的普通值才会被回调函数访问。回调函数永远不会调用树中具有子节点的节点(即子数组)。这使得此函数在大多数实际情况下都不可用。
(PHP 5, PHP 7, PHP 8)
array_walk_recursive — 递归地将用户函数应用于数组的每个成员
将用户定义的 callback
函数应用于 array
的每个元素。此函数将递归进入更深层的数组。
array
输入数组。
callback
通常,callback
接收两个参数。array
参数的值是第一个参数,键/索引是第二个参数。
注意:
如果
callback
需要使用数组的实际值,请将callback
的第一个参数指定为 引用。然后,对这些元素进行的任何更改都将在原始数组本身中进行。
arg
如果提供了可选的 arg
参数,它将作为第三个参数传递给 callback
。
始终返回 true
。
示例 #1 array_walk_recursive() 示例
<?php
$sweet = array('a' => 'apple', 'b' => 'banana');
$fruits = array('sweet' => $sweet, 'sour' => 'lemon');
function test_print($item, $key)
{
echo "$key holds $item\n";
}
array_walk_recursive($fruits, 'test_print');
?>
上面的示例将输出
a holds apple b holds banana sour holds lemon
您可能会注意到键 'sweet
' 从未显示。任何持有 array 的键都不会传递给函数。
由于这仅在其中一个示例的输出脚注中提到,我认为应该明确说明
* 此函数仅访问叶节点 *
也就是说,如果您有一个包含子数组的子数组的数组树,则只有树叶上的普通值才会被回调函数访问。回调函数永远不会调用树中具有子节点的节点(即子数组)。这使得此函数在大多数实际情况下都不可用。
如何使用 userdata 参数从递归函数内部修改外部变量。
<?php
$arr = [
'one' => ['one_one' => 11, 'one_two' => 12],
'two' => 2
];
$counter = 0;
// 不持久化
array_walk_recursive( $arr, function($value, $key, $counter) {
$counter++;
echo "$value : $counter";
}, $counter);
echo "counter : $counter";
// 结果
// 11 : 1
// 12 : 1
// 2 : 1
// counter: 0
// 仅在同一数组节点中持久化
array_walk_recursive( $arr, function($value, $key, &$counter) {
$counter++;
echo "$value : $counter";
}, $counter);
// 结果
// 11 : 1
// 12 : 2
// 2 : 1
// counter : 0
// 完全持久化。使用 'use' 关键字
array_walk_recursive( $arr, function($value, $key) use (&$counter) {
$counter++;
echo "$value : $counter";
}, $counter);
echo "counter : $counter";
// 结果
// 11 : 1
// 12 : 2
// 2 : 3
// counter : 3
如果您想更改现有多维数组的值,如上面注释中所说,您需要将第一个参数指定为引用。这意味着确保在 $item
变量前面加上一个按位与符号 (&),如以下良好示例所示。
不幸的是,提供的 PHP 示例没有这样做。实际上,我花了一段时间才弄清楚为什么我的函数没有更改原始数组,即使我使用的是按引用传递。
这里有一个提示:不要从函数中返回任何值!只需更改按引用传递的 $item
的值即可。这相当违反直觉,因为绝大多数函数都会返回值。
<?php
// array_walk_recursive 无法更改您的数组,除非您按引用传递。
// 不要从您的过滤器函数中返回值,即使乍一看它很有道理!
function bad_example($item,$key){
if($key=='test'){
return 'PHP Rocks'; // 不要这样做
}else{
return $item; // 也不要这样做
}
}
// array_walk_recursive 按引用传递示例
function good_example(&$item,$key){
if($key=='test'){
$item='PHP Rocks'; // 这样做!
}
}
$arr = array('a'=>'1','b'=>'2','test'=>'Replace This');
array_walk_recursive($arr,'bad_example');
var_dump($arr);
/**
* 没有错误,但打印...
* array('a'=>'1','b'=>'2','test'=>'Replace This');
*/
array_walk_recursive($arr,'good_example');
var_dump($arr);
/**
* 打印...
* array('a'=>'1','b'=>'2','test'=>'PHP Rocks');
*/
?>
如果按引用传递并在返回之前修改 $item,从您的函数中返回值确实有效,但如果您尝试这样做,即使在像这里这样小的示例上,您也会非常快地占用内存。
您可能首先尝试的另一件愚蠢的事情是这样的
<?php
// 抵制这样做的冲动,它不起作用。
$filtered = array_walk_recursive($unfiltered,'filter_function');
?>
当然,$filtered 之后只是 TRUE,而不是您想要的过滤后的结果。哦,它确实递归地运行了您的函数,但只更改了局部函数作用域中的所有值,并如文档所述返回一个布尔值。
我使用 RecursiveIteratorIterator 以及 CATCH_GET_CHILD 参数来迭代叶子和节点,而不是使用 array_walk_recursive 函数
<?php
// 迭代叶子和节点
foreach (new RecursiveIteratorIterator(new RecursiveArrayIterator($candidate), RecursiveIteratorIterator::CATCH_GET_CHILD) as $key => $value) {
echo 'My node ' . $key . ' with value ' . $value . PHP_EOL;
}
?>
描述中说“如果 funcname 需要使用数组的实际值,请将 funcname 的第一个参数指定为引用”。这并不一定有用,因为您调用的函数可能是内置的(例如 trim 或 strip_tags)。一种选择是像这样创建这些函数的版本。
<?php
function trim_by_reference(&$string) {
$string = trim($string);
}
?>
这种方法的缺点是您需要为可能调用的每个函数创建一个包装函数。相反,我们可以使用 PHP 5.3 的内联函数语法来创建一个新的 array_walk_recursive 版本。
<?php
/**
* 此函数的行为与 array_walk_recursive 完全相同,只是它假装其调用的函数
* 其调用用结果替换值。
*
* @param $array 数组的第一个值将作为主要参数传递到 $function 中
* @param $function 要在数组中的每个元素上调用的函数,递归地
* @param $parameters 要附加到函数的附加参数的可选数组
*
* 例如使用更改 $array 以从每个值获取第二个、第三个和第四个字符
* array_walk_recursive_referential($array, "substr", array("1","3"));
*/
function array_walk_recursive_referential(&$array, $function, $parameters = array()) {
$reference_function = function(&$value, $key, $userdata) {
$parameters = array_merge(array($value), $userdata[1]);
$value = call_user_func_array($userdata[0], $parameters);
};
array_walk_recursive($array, $reference_function, array($function, $parameters));
}
?>
这里的优势是我们只显式定义一个包装函数,而不是可能数十个。
以下代码将某种扁平化的数组返回到 $results 数组中,在较新的 PHP 版本中会引发错误“PHP 致命错误:调用时按引用传递已被删除”
<?php
$results = array();
function example_function ($item, $key, &$arr_values)
{
$arr_values[$key] = $item;
}
array_walk_recursive($data, 'example_function', &$results);
print_r($results);
?>
这可以通过使用匿名函数来修复
<?php
$results = array();
array_walk_recursive($data, function ($item, $key) use (&$results){$results[$key] = $item;});
print_r($results);
?>
这是一个更通用的解决方案,用于修改叶子所属的数组。您可以取消设置当前键,或添加同级,等等。
<?php
/**
* 修改后的 array_walk_recursive 版本,将数组传递到回调函数中
* 回调函数可以通过为参数指定引用来修改数组或值。
*
* @param array 输入数组。
* @param callable $callback($value, $key, $array)
*/
function array_walk_recursive_array(array &$array, callable $callback) {
foreach ($array as $k => &$v) {
if (is_array($v)) {
array_walk_recursive_array($v, $callback);
} else {
$callback($v, $k, $array);
}
}
}
?>
多维数组到单维数组
$array=[1=>[2,5=>[4,2],[7,8=>[3,6]],5],4];
$arr=[];
array_walk_recursive($array, function($k){global $arr; $arr[]=$k;});
print_r($arr);
输出
Array ( [0] => 2 [1] => 4 [2] => 2 [3] => 7 [4] => 3 [5] => 6 [6] => 5 [7] => 4 )
array_walk_recursive 本身无法取消设置值。即使您可以按引用传递数组,在回调中取消设置值只会取消设置该作用域中的变量。
<?php
/**
* http://uk1.php.net/array_walk_recursive 用于从数组中删除节点的实现。
*
* @param array 输入数组。
* @param callable $callback 函数必须返回一个布尔值,指示是否要删除该节点。
* @return array
*/
function walk_recursive_remove (array $array, callable $callback) {
foreach ($array as $k => $v) {
if (is_array($v)) {
$array[$k] = walk_recursive_remove($v, $callback);
} else {
if ($callback($v, $k)) {
unset($array[$k]);
}
}
}
return $array;
}
?>
上述函数的最新实现可以在 https://github.com/gajus/marray/blob/master/src/marray.php#L116. 中找到。
我一直在寻找如何更改数组的值,因为在新的 PHP 版本中不能再通过引用传递数组了,这里有一个简单的解决方案
<?php
array_walk_recursive(
$myArray,
function (&$value) {
if (/*some condition*/) {
$value = 'New value';
}
}
);
?>
之后,$myArray 将会使用新值进行修改。
我需要在一个结构未知的数组中添加或修改值。我原本希望使用 array_walk_recursive 来完成这项任务,但由于我还需要添加新的节点,所以我找到了一个替代方案。
<?php
/**
* 在数组的任何深度设置键值对。
* @param $data 要添加/修改的键值对数组
* @param $array 要操作的数组
*/
function setNodes($data, &$array)
{
$separator = '.'; // 将此设置为您的键中不会出现的任何字符串
foreach ($data as $name => $value) {
if (strpos($name, $separator) === false) {
// 如果数组不包含特殊的分割字符,则直接设置键值对。
// 如果 $value 是一个数组,您当然可以很好地设置嵌套的键值对。
$array[$name] = $value;
} else {
// 在这种情况下,我们试图定位一个特定的嵌套节点,而不覆盖任何其他兄弟节点/祖先节点。
// 该节点或其祖先节点可能还不存在。
$keys = explode($separator, $name);
// 设置树的根节点。
$opt_tree =& $array;
// 使用指定的键开始遍历树。
while ($key = array_shift($keys)) {
// 如果当前键之后还有更多的键...
if ($keys) {
if (!isset($opt_tree[$key]) || !is_array($opt_tree[$key])) {
// 如果该节点不存在,则创建它。
$opt_tree[$key] = array();
}
// 重新定义树的“根节点”到该节点(通过引用赋值),然后处理下一个键。
$opt_tree =& $opt_tree[$key];
} else {
// 这是要检查的最后一个键,因此赋值。
$opt_tree[$key] = $value;
}
}
}
}
}
?>
示例用法
<?php
$x = array();
setNodes(array('foo' => 'bar', 'baz' => array('quux' => 42, 'hup' => 101)), $x);
print_r($x); // $x 的结构与第一个参数相同
setNodes(array('jif.snee' => 'hello world', 'baz.quux.wek' => 5), $x);
print_r($x); // 添加了 $x['jif']['snee'] 并修改了 $x['baz']['quux'] 为数组('wek' => 5)
?>
一个简单的解决方案,用于遍历嵌套数组以获取指定键的最后一个设置值
<?php
$key = 'blah';
$val = null;
array_walk_recursive( $your_array,
function($v, $k, $u) { if($k === $u[0]) $u[1] = $v; },
[$key ,&$val] );
echo "$key = $val";
?>
<?
function my_array_map() {
$args = func_get_args();
$arr = array_shift($args);
foreach ($args as $fn) {
$nfn = create_function('&$v, $k, $fn', '$v = $fn($v);');
array_walk_recursive($arr, $nfn, $fn);
}
return $arr;
}
?>
它将一个数组作为第一个参数,并将函数作为其他参数。它将这些函数递归地应用于数组
简单的 array_walk_recursive
// 示例变量
$myArray = Array(
Array('keyA1' => ' textA1 ', 'keyA2' => ' textA2 '),
Array('keyB1' => ' textB1 ', 'sub' =>
Array('keyB1_sub1' => ' textB1_sub1 '),
Array('keyB1_sub2' => ' textB1_sub2 ')
),
Array('keyC1' => ' textC1 ', 'keyC2' => ' textC2 '),
Array('keyD1' => ' textD1 ', 'keyD2' => ' textD2 '),
Array('keyE1' => ' textE1 ', 'keyE2' => ' textE2 ')
);
// 用于“trim”的函数(或您的函数,使用相同的结构)
function trimming($data) {
if (gettype($data) == 'array')
return array_map("trimming", $data);
else
return trim($data);
}
// 获取数组
$myArray = array_map("trimming", $myArray);
// 显示修剪后的数组
var_dump($myArray);
/*
结果
array (size=5)
0 =>
array (size=2)
'keyA1' => string 'textA1' (length=6)
'keyA2' => string 'textA2' (length=6)
1 =>
array (size=3)
'keyB1' => string 'textB1' (length=6)
'sub' =>
array (size=1)
'keyB1_sub1' => string 'textB1_sub1' (length=11)
0 =>
array (size=1)
'keyB1_sub2' => string 'textB1_sub2' (length=11)
2 =>
array (size=2)
'keyC1' => string 'textC1' (length=6)
'keyC2' => string 'textC2' (length=6)
3 =>
array (size=2)
'keyD1' => string 'textD1' (length=6)
'keyD2' => string 'textD2' (length=6)
4 =>
array (size=2)
'keyE1' => string 'textE1' (length=6)
'keyE2' => string 'textE2' (length=6)
*/
我决定对之前 PHP 4 兼容版本的 array_walk_recursive() 进行扩展,使其能够在类中和作为独立函数使用。这两种情况都由以下函数处理,我从 omega13a at sbcglobal dot net 修改了该函数。
以下示例是在类中使用。要作为独立函数使用,请将其从类中取出并重命名。(示例:array_walk_recursive_2)
<?php
class A_Class {
function array_walk_recursive(&$input, $funcname, $userdata = '') {
if(!function_exists('array_walk_recursive')) {
if(!is_callable($funcname))
return false;
if(!is_array($input))
return false;
foreach($input as $key=>$value) {
if(is_array($input[$key])) {
if(isset($this)) {
eval('$this->' . __FUNCTION__ . '($input[$key], $funcname, $userdata);');
} else {
if(@get_class($this))
eval(get_class() . '::' . __FUNCTION__ . '($input[$key], $funcname, $userdata);');
else
eval(__FUNCTION__ . '($input[$key], $funcname, $userdata);');
}
} else {
$saved_value = $value;
if(is_array($funcname)) {
$f = '';
for($a=0; $a<count($funcname); $a++)
if(is_object($funcname[$a])) {
$f .= get_class($funcname[$a]);
} else {
if($a > 0)
$f .= '::';
$f .= $funcname[$a];
}
$f .= '($value, $key' . (!empty($userdata) ? ', $userdata' : '') . ');';
eval($f);
} else {
if(!empty($userdata))
$funcname($value, $key, $userdata);
else
$funcname($value, $key);
}
if($value != $saved_value)
$input[$key] = $value;
}
}
return true;
} else {
array_walk_recursive($input, $funcname, $userdata);
}
}
function kv_addslashes(&$v, $k) {
$v = addslashes($v);
}
}
?>
用法
<?php
$arr = array(
'a' => '"Hello World"',
'b' => "'Hello World'",
'c' => "Hello 'Worl\"d",
'd' => array(
'A' => 'H"e"l"l"o" "W"o"r"l"d'
)
);
$class = new A_Class();
$class->array_walk_recursive($arr, array(&$class, 'kv_addslashes'));
print_r($arr);
?>
从 PHP 5.3.0 开始,在使用 foo(&$a); 时,您会收到一个警告,提示“调用时按引用传递”已弃用。从 PHP 5.4.0 开始,调用时按引用传递已被移除,因此使用它会导致致命错误。
普通函数解决方案
//1,2,2,3,6,7,3,1,4,2
$arr=[
[1,2],
[2,3],
6,7,[3,1,[4,2]]
];
function a($array){
static $res=[];
foreach($array as $val){
if(is_array($val)){
a($val);
}else{
$res[]=$val;
}
}
return $res;
}
print_r(a($arr));
多维数组到单维数组
$array=[1=>[2,5=>[4,2],[7,8=>[3,6]],5],4];
$arr=[];
array_walk_recursive($array, function($k){global $arr; $arr[]=$k;});
print_r($arr);
输出
Array ( [0] => 2 [1] => 4 [2] => 2 [3] => 7 [4] => 3 [5] => 6 [6] => 5 [7] => 4 )
要将数组中的所有值转换为 UTF8,请执行以下操作
<?php
function convert_before_json(&$item, &$key)
{
$item=utf8_encode($item);
}
array_walk_recursive($your_array,"convert_before_json");
?>
此函数存在一个严重错误,截至 PHP 5.2.5 版本尚未修复。在您调用它之后,它可能会意外修改您的原始数组。通过阅读以下内容,您可以避免数小时的挫折。
错误出现在这里:http://bugs.php.net/bug.php?id=42850, 并且看起来它将在 5.3 中修复。
如果您遍历的数组包含其他数组元素,它们将被转换为引用。即使回调函数没有按引用获取其第一个参数,并且对值不做任何操作,也会发生这种情况。
例如,试试这个
<?php
$data = array ('key1' => 'val1', 'key2' => array('key3' => 'val3'));
function foo($item, $key){}
var_dump($data);
?>
原始数组没有引用。现在试试这个
<?php
array_walk_recursive($data,'foo');
var_dump($data);
?>
现在 key2 是一个引用,而不仅仅是一个数组。因此,如果您执行此操作
<?php
function test($item){$item['key2'] = array();}
test($data);
var_dump($data);
?>
您会看到 test 修改了 $data,即使它不应该这样做。
一种解决方法是在调用 array_walk_recursive 后立即对数组进行深度复制,如下所示
<?php
function array_duplicate($input) {
if (!is_array($input)) return $input;
$output = array();
foreach ($input as $key => $value) {
$output[$key] = array_duplicate($value);
}
return $output;
}
array_walk_recursive($data,'foo');
$data = array_duplicate($data);
var_dump($data);
?>
完成之后,引用就消失了。
用法
$nd = $newsApp2->dataSources();
//walkArray ($nd, 'walkArray_printKey', 'walkArray_printValue');
// 打印整个数组
$x = chaseToPath ($nd, 'RSS_list/English News',false);
walkArray ($x, 'walkArray_printKey', 'walkArray_printValue');
// 打印 $nd['RSS_list']['English News'] 下的所有内容
function &chaseToPath (&$wm, $path, $create=false) {
//var_dump ($create); die();
//echo '$wm=<pre>'; var_dump ($wm);echo '</pre>'; //die();
//$path = str_replace ('/', '/d/', $path);
//$path .= '/d';
$nodes = explode ('/', $path);
$chase = &chase ($wm, $nodes, $create);
//echo '$wm=<pre>'; var_dump ($wm);echo '</pre>'; die();
/*
$dbg = array (
'$path' => $path,
'$nodes' => $nodes,
'$wm' => $wm,
'$chase' => $chase
);
echo '$dbg=<pre style="background:red;color:yellow;">'; var_dump ($dbg); echo '</pre>';
*/
//die();
$false = false;
if (good($chase)) {
$arr = &result($chase);
return $arr;
} else return $false;
}
function &chase (&$arr, $indexes, $create=false) {
if (false) {
echo 'sitewide/functions.php --- $arr=<pre>'; var_dump ($arr); echo '</pre>';
echo 'sitewide/functions.php --- $indexes=<pre>'; var_dump ($indexes); echo '</pre>';
echo 'sitewide/functions.php --- $create=<pre>'; var_dump ($create); echo '</pre>';
}
$r = &$arr;
foreach ($indexes as $idx) {
//echo 'sitewide/functions.php --- $idx=<pre>'; var_dump ($idx); var_dump (array_key_exists($idx,$r)); var_dump ($r); echo '</pre>';
if (
is_array($r)
&& (
$create===true
|| array_key_exists($idx,$r)
)
) {
if ($create===true && !array_key_exists($idx,$r)) $r[$idx]=array();
//echo 'sitewide/functions.php --- $idx=<pre>'; var_dump ($idx); echo '</pre>';
$r = &$r[$idx];
} else {
$err = array(
'msg' => '无法遍历完整树',
'vars' => array(
'$idx--error'=>$idx,
'$indexes'=>$indexes,
'$arr'=>$arr
)
);
badResult (E_USER_NOTICE, $err);
$ret = false; // BUG #2 已经修复
return $ret;
}
}
//echo 'sitewide/functions.php --- $r=<pre>'; var_dump ($r); echo '</pre>';
return goodResult($r);
}