由於這僅在其中一個範例的輸出註腳中提到,我覺得應該明確指出
* 此函式僅拜訪葉節點 *
也就是說,如果您有一個子陣列的子陣列的陣列樹,則只有樹的葉子上的純值會被回調函式拜訪。回調函式永遠不會針對樹中具有子節點(即子陣列)的節點調用。這會導致此函式在大多數實際情況下都無法使用。
(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 變數前面加上 & 符號,如下面的 good_example 所示。
不幸的是,給定的 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,而不是您想要的篩選結果。喔,它確實以遞迴方式執行了您的函式,但只變更了本機函式範圍內的所有值,並如文件所述傳回布林值。
我使用帶有參數 CATCH_GET_CHILD 的 RecursiveIteratorIterator 來疊代葉節點和節點,而不是 array_walk_recursive 函式
<?php
// 疊代葉節點和節點
foreach (new RecursiveIteratorIterator(new RecursiveArrayIterator($candidate), RecursiveIteratorIterator::CATCH_GET_CHILD) as $key => $value) {
echo '我的節點 ' . $key . ' 值為 ' . $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 Fatal error: Call-time pass-by-reference has been removed (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 (/*某些條件*/) {
$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'] 修改為 array('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' => 'Could not walk the full tree',
'vars' => array(
'$idx--error'=>$idx,
'$indexes'=>$indexes,
'$arr'=>$arr
)
);
badResult (E_USER_NOTICE, $err);
$ret = false; // BUG #2 squashed
return $ret;
}
}
//echo 'sitewide/functions.php --- $r=<pre>'; var_dump ($r); echo '</pre>';
return goodResult($r);
}