Documentation on array_walk_recursive
array_walk_recursive = Apply a user function recursively to every member of an array
Applies the user-defined callback function to each element of the array. This function will recurse into deeper arrays.
array The input array. callback Typically, callback takes on two parameters. The array parameter's value being the first, and the key/index second. Note: If callback needs to be working with the actual values of the array, specify the first parameter of callback as a reference. Then, any changes made to those elements will be made in the original array itself. userdata If the optional userdata parameter is supplied, it will be passed as the third parameter to the callback.
Usage, params, and more on array_walk_recursive
bool array_walk_recursive ( array &$array
, callable $callback
[, mixed $userdata
= NULL
] )
array
The input array. callback
Typically, callback
takes on two parameters. The array
parameter's value being the first, and the key/index second. Note: If callback
needs to be working with the actual values of the array, specify the first parameter of callback
as a reference. Then, any changes made to those elements will be made in the original array itself. userdata
If the optional userdata
parameter is supplied, it will be passed as the third parameter to the callback
.
Returns TRUE
on success or FALSE
on failure.
Notes and warnings on array_walk_recursive
Basic example of how to use: array_walk_recursive
Example #1 array_walk_recursive() example
<?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');
?>
The above example will output:
a holds apple b holds banana sour holds lemon
You may notice that the key 'sweet' is never displayed. Any key that holds an array will not be passed to the function.
Other code examples of array_walk_recursive being used
A simple solution for walking a nested array to obtain the last set value of a specified key:
<?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";
?>
If you are wanting to change the values of an existing multi-dimensional array, as it says above in the note, you need to specify the first argument as a reference. All that means is, be sure to precede the $item variable with an ampersand (&) as in the good_example below.
Unfortunately the PHP example given doesn't do this. It actually took me a while to figure out why my function wasn't changing the original array, even though I was passing by reference.
Here's the tip: Don't return any value from the function! Just change the value of $item that you passed in by reference. This is rather counter-intuitive since the vast majority of functions return a value.
<?php
// array_walk_recursive fails to change your array unless you pass by reference.
// Don't return values from your filter function, even though it's quite logical at a glance!
function bad_example($item,$key){
if($key=='test'){
return 'PHP Rocks'; // Don't do it
}else{
return $item; // Don't do this either
}
}
// array_walk_recursive pass-by-reference example
function good_example(&$item,$key){
if($key=='test'){
$item='PHP Rocks'; // Do This!
}
}
$arr = array('a'=>'1','b'=>'2','test'=>'Replace This');
array_walk_recursive($arr,'bad_example');
var_dump($arr);
/**
* no errors, but prints...
* array('a'=>'1','b'=>'2','test'=>'Replace This');
*/
array_walk_recursive($arr,'good_example');
var_dump($arr);
/**
* prints...
* array('a'=>'1','b'=>'2','test'=>'PHP Rocks');
*/
?>
Returning a value from your function does work if you pass by reference and modify $item before you return, but you will eat up memory very, very fast if you try it, even on an example as small as the one here.
One other silly thing you might try first is something like this:
<?php
// Resist the urge to do this, it doesn't work.
$filtered = array_walk_recursive($unfiltered,'filter_function');
?>
Of course, $filtered is just TRUE afterwards, not the filtered results you were wanting. Oh, it ran your function recursively alright, but changed all the values in the local function scope only and returns a boolean as the documentation states.
The description says "If funcname needs to be working with the actual values of the array, specify the first parameter of funcname as a reference." This isn't necessarily helpful as the function you're calling might be built in (e.g. trim or strip_tags). One option would be to create a version of these like so.
<?php
function trim_by_reference(&$string) {
$string = trim($string);
}
?>
The downside to this approach is that you need to create a wrapper function for each function you might want to call. Instead, we can use PHP 5.3's inline function syntax to create a new version of array_walk_recursive.
<?php
/**
* This function acts exactly like array_walk_recursive, except that it pretends that the function
* its calling replaces the value with its result.
*
* @param $array The first value of the array will be passed into $function as the primary argument
* @param $function The function to be called on each element in the array, recursively
* @param $parameters An optional array of the additional parameters to be appeneded to the function
*
* Example usage to alter $array to get the second, third and fourth character from each value
* 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));
}
?>
The advantage here is that we only explicitly define one wrapper function instead of potentially dozens.
array_walk_recursive itself cannot unset values. Even though you can pass array by reference, unsetting the value in the callback will only unset the variable in that scope.
<?php
/**
* http://uk1.php.net/array_walk_recursive implementation that is used to remove nodes from the array.
*
* @param array The input array.
* @param callable $callback Function must return boolean value indicating whether to remove the node.
* @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;
}
?>
An up to date implementation of the above function can be looked up from https://github.com/gajus/marray/blob/master/src/marray.php#L116.
I use RecursiveIteratorIterator with parameter CATCH_GET_CHILD to iterate on leafs AND nodes instead of array_walk_recursive function :
<?php
// Iteration on leafs AND nodes
foreach (new RecursiveIteratorIterator(new RecursiveArrayIterator($candidate), RecursiveIteratorIterator::CATCH_GET_CHILD) as $key => $value) {
echo 'My node ' . $key . ' with value ' . $value . PHP_EOL;
}
?>
I needed to add or modify values in an array with unknown structure. I was hoping to use array_walk_recursive for the task, but because I was also adding new nodes I came up with an alternate solution.
<?php
/**
* Sets key/value pairs at any depth on an array.
* @param $data an array of key/value pairs to be added/modified
* @param $array the array to operate on
*/
function setNodes($data, &$array)
{
$separator = '.'; // set this to any string that won't occur in your keys
foreach ($data as $name => $value) {
if (strpos($name, $separator) === false) {
// If the array doesn't contain a special separator character, just set the key/value pair.
// If $value is an array, you will of course set nested key/value pairs just fine.
$array[$name] = $value;
} else {
// In this case we're trying to target a specific nested node without overwriting any other siblings/ancestors.
// The node or its ancestors may not exist yet.
$keys = explode($separator, $name);
// Set the root of the tree.
$opt_tree =& $array;
// Start traversing the tree using the specified keys.
while ($key = array_shift($keys)) {
// If there are more keys after the current one...
if ($keys) {
if (!isset($opt_tree[$key]) || !is_array($opt_tree[$key])) {
// Create this node if it doesn't already exist.
$opt_tree[$key] = array();
}
// Redefine the "root" of the tree to this node (assign by reference) then process the next key.
$opt_tree =& $opt_tree[$key];
} else {
// This is the last key to check, so assign the value.
$opt_tree[$key] = $value;
}
}
}
}
}
?>
Sample usage:
<?php
$x = array();
setNodes(array('foo' => 'bar', 'baz' => array('quux' => 42, 'hup' => 101)), $x);
print_r($x); // $x has the same structure as the first argument
setNodes(array('jif.snee' => 'hello world', 'baz.quux.wek' => 5), $x);
print_r($x); // added $x['jif']['snee'] and modified $x['baz']['quux'] to be array('wek' => 5)
?>