Reputation: 212452
For a while now, I've been using a "traditional" recursive function to flatten multi-dimensional arrays such as
$baseArray = array(array('alpha'),
array('beta','gamma'),
array(),
array(array('delta','epsilon'),
array('zeta',array('eta',
'theta'
),
),
),
array('iota'),
);
to a simple 1-d array.
Last night, I thought I'd take a look at using array_walk_recursive() to see if I could make it more efficient and cleaner.
My first attempt wasn't very successful:
function flattenArray($arrayValue, $arrayKey, &$flatArray) {
$flatArray[] = $arrayValue;
}
$flattenedArray = array();
array_walk_recursive($baseArray,'flattenArray',$flattenedArray);
I thought it should work, but all I got was a series of errors:
Warning: Cannot use a scalar value as an array in C:\xampp\htdocs\arrayTest.php on line 16
and a result of:
array(0) { }
Type hinting in my flattenArray() function gave me
Catchable fatal error: Argument 3 passed to flattenArray() must be an array, integer given in C:\xampp\htdocs\arrayTest.php on line 16
Using a closure gave identical results
The only way I could get it to work (without recourse to using a global or a static for my flattenedArray) was using call-time pass-by-reference:
function flattenArray($arrayValue, $arrayKey, $flatArray) {
$flatArray[] = $arrayValue;
}
$flattenedArray = array();
array_walk_recursive($baseArray,'flattenArray',&$flattenedArray);
which produces the correct result
array(9) { [0]=> string(5) "alpha" [1]=> string(4) "beta" [2]=> string(5) "gamma" [3]=> string(5) "delta" [4]=> string(7) "epsilon" [5]=> string(4) "zeta" [6]=> string(3) "eta" [7]=> string(5) "theta" [8]=> string(4) "iota" }
but gives me a not-unexpected warning
Deprecated: Call-time pass-by-reference has been deprecated in C:\xampp\htdocs\arrayTest.php on line 22
I know PHP is a quirky language, but this seems a bit extreme. The documentation clearly shows that the first parameter to array_walk_recursive is pass-by-reference, but it seems that additional arguments can only be pass-by-reference at call-time. Weird!
PHP version is 5.3.8
Any suggestions as to how I can use array_walk_recursive to flatten my array correctly, without getting deprecated errors (besides filing a bug report).
EDIT
P.S.
I am aware that I can bypass this problem using a closure:
$flattenedArray = array();
array_walk_recursive($baseArray, function($arrayValue, $arrayKey) use(&$flattenedArray){ $flattenedArray[] = $arrayValue; } );
var_dump($flattenedArray);
but as this is required for a library which currently allows use with PHP 5.2.0, it's not a practical option to use a feature that requires a significantly later version of PHP
Upvotes: 6
Views: 2562
Reputation: 101936
Think about it this way: You pass $flatArray
into array_walk_recursive
by value. array_walk_recursive
will then further pass the argument by reference to your function. But as it was passed to array_walk_recursive
by value, the reference will already point to a different value.
I know, this might seem like a strange limitation, but when you think about it, it's quite logical too.
By the way, I think you accidentally also found another issue with this, it actually looks like serious memory corruption (look at the third elements of the array printed @ http://codepad.viper-7.com/ZYNrNd). I will look into this.
On a side note, an easy way to flatten is using a RecursiveArrayIterator
:
$flattenedArray = array();
foreach (new RecursiveIteratorIterator(
new RecursiveArrayIterator($baseArray),
RecursiveIteratorIterator::LEAVES_ONLY
) as $value) {
$flattenedArray[] = $value;
}
Upvotes: 7
Reputation: 77420
Since your solutions are version-specific (according to the documentation, PHP 5.2 shouldn't complain about call-time pass-by-reference), one option is to create different scripts containg each implementation, then have another script conditionally include the script that defines the flattening function (and any other version-specific code) depending on the PHP version.
if (! defined('PHP_VERSION_ID')) {
$version = explode('.', PHP_VERSION);
define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2]));
if (PHP_VERSION_ID < 50207) {
define('PHP_MAJOR_VERSION', $version[0]);
define('PHP_MINOR_VERSION', $version[1]);
define('PHP_RELEASE_VERSION', $version[2]);
}
}
if (PHP_VERSION_ID < 50300) {
include_once('php-5.2-.php');
} else {
include_once('php-5.3+.php');
}
Upvotes: 1
Reputation: 340
Pass by reference at call time is deprecated:
https://www.php.net/manual/en/ini.core.php#ini.allow-call-time-pass-reference
This isn't noted in the array_walk_recursive documentation because it isn't specific to that function.
One thing you could do is pass an object and method name, rather than a function name, as the callback, and maintain the state of the flattened array as a member of that object.
e.g.
class ArrayFlattener
{
//implementation and array state
}
$flattener = new ArrayFlattener();
array_walk_recursive($baseArray, array($flattener, 'flatten'));
print_r($flattener->getFlattenedArray());
Upvotes: 0
Reputation: 212452
Not particularly helpful at this stage.
Reading through the PHP docs, I've found that call_user_func() has a note against it's description of parameter arguments:
Note that the parameters for call_user_func() are not passed by reference.
but array_walk_recursive() has no such notice.
This may be purely a documentation issue... if I'd seen a similar notice in the array_walk_recursive() documentation, I probably wouldn't have tried it (although the curious might have tried regardless, just to see what happened).
However, I don't understand why either case shouldn't accept pass-by-reference arguments in the callback function definition. This does feel a bit like a step backward.... a language feature that did work (albeit using the call-time pass-by-reference) no longer does so, without relying on that deprecated feature.
Upvotes: 1