引用传递不适用于 array_walk_recursive 的附加参数,除非它是已弃用的调用时引用传递

发布于 2024-12-22 04:18:56 字数 2594 浏览 0 评论 0原文

一段时间以来,我一直在使用“传统”递归函数来展平多维数组,例如

$baseArray = array(array('alpha'),
                   array('beta','gamma'),
                   array(),
                   array(array('delta','epsilon'),
                         array('zeta',array('eta',
                                            'theta'
                                           ),
                              ),
                        ),
                   array('iota'),
                  );

简单的一维数组。

昨晚,我想看看使用 array_walk_recursive( ) 看看我是否可以让它更高效、更干净。

我的第一次尝试不是很成功:

function flattenArray($arrayValue, $arrayKey, &$flatArray) {
    $flatArray[] = $arrayValue;
}


$flattenedArray = array();
array_walk_recursive($baseArray,'flattenArray',$flattenedArray);

我认为它应该可行,但我得到的只是一系列错误:

Warning: Cannot use a scalar value as an array in C:\xampp\htdocs\arrayTest.php on line 16

以及结果:

array(0) { }

我的 flattenArray() 函数中的类型提示给了我

Catchable fatal error: Argument 3 passed to flattenArray() must be an array, integer given in C:\xampp\htdocs\arrayTest.php on line 16

使用闭包给出了相同的结果

我的唯一方法可以让它工作(无需对我的 flattenedArray 使用全局或静态)正在使用调用时传递引用:

function flattenArray($arrayValue, $arrayKey, $flatArray) {
    $flatArray[] = $arrayValue;
}


$flattenedArray = array();
array_walk_recursive($baseArray,'flattenArray',&$flattenedArray);

它会产生正确的结果,

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" }

但会给我一个并非意外的警告

Deprecated: Call-time pass-by-reference has been deprecated in C:\xampp\htdocs\arrayTest.php on line 22

我知道 PHP 是一种古怪的语言,但这似乎是一个有点极端。文档清楚地表明 array_walk_recursive 的第一个参数是按引用传递的,但似乎其他参数只能在调用时按引用传递。诡异的!

PHP 版本是 5.3.8

有关如何使用 array_walk_recursive 正确展平数组的任何建议,而不会出现已弃用的错误(除了提交错误报告之外)。

编辑

PS

我知道我可以使用闭包绕过这个问题:

$flattenedArray = array();
array_walk_recursive($baseArray, function($arrayValue, $arrayKey) use(&$flattenedArray){ $flattenedArray[] = $arrayValue; } );
var_dump($flattenedArray);

但由于这是当前允许与 PHP 5.2.0 一起使用的库所必需的,因此使用以下功能并不是一个实际的选择需要较新版本的 PHP

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

从来不烧饼 2024-12-29 04:18:56

这样想:您将 $flatArray 传递到 array_walk_recursive 按值。然后,array_walk_recursive通过引用进一步将参数传递给您的函数。但由于它是按值传递给 array_walk_recursive 的,因此引用已经指向不同的值。

我知道,这可能看起来是一个奇怪的限制,但当你仔细想想,它也很合乎逻辑。

顺便说一句,我认为您无意中还发现了另一个问题,它实际上看起来像严重的内存损坏(看看打印的数组的第三个元素@ http://codepad.viper-7.com/ZYNrNd)。我会调查一下。

顺便说一句,一种简单的扁平化方法是使用 RecursiveArrayIterator:

$flattenedArray = array();
foreach (new RecursiveIteratorIterator(
             new RecursiveArrayIterator($baseArray),
             RecursiveIteratorIterator::LEAVES_ONLY
         ) as $value) {
    $flattenedArray[] = $value;
}

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;
}
那一片橙海, 2024-12-29 04:18:56

在这个阶段并不是特别有帮助。

阅读 PHP 文档,我发现 call_user_func()< /a> 对参数参数的描述有一个注释:

注意call_user_func()的参数不是通过
参考。

array_walk_recursive() 没有这样的通知。

这可能纯粹是一个文档问题...如果我在 array_walk_recursive() 文档中看到类似的通知,我可能不会尝试它(尽管好奇的人可能会尝试无论如何,只是为了看看发生了什么)。

但是,我不明白为什么这两种情况都不应该在回调函数定义中接受按引用传递参数。这确实感觉有点像倒退......一个确实有效的语言功能(尽管使用调用时传递引用)不再有效,而不依赖于那个已弃用的功能。

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.

孤君无依 2024-12-29 04:18:56

由于您的解决方案是特定于版本的(根据文档,PHP 5.2 不应该抱怨调用时间 传递引用),一种选择是创建包含每个实现的不同脚本,然后让另一个脚本有条件地包含定义扁平化函数(以及任何其他特定于版本的代码)的脚本,具体取决于PHP 版本。

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');
}

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');
}
一生独一 2024-12-29 04:18:56

不推荐在调用时通过引用传递:

https://www.php.net/manual/en/ini.core.php#ini.allow-call-time-pass-reference

这在 array_walk_recursive 文档中没有注明,因为它不是不具体于此 功能。

您可以做的一件事是传递对象和方法名称(而不是函数名称)作为回调,并将展平数组的状态维护为该对象的成员。

例如

class ArrayFlattener
{
    //implementation and array state
}

$flattener = new ArrayFlattener();
array_walk_recursive($baseArray, array($flattener, 'flatten'));

print_r($flattener->getFlattenedArray());

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());
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文