PHP 通过引用返回数组,但也可为 null

PHP return arrays by reference, but also be nullable

提问人:Fawfulcopter 提问时间:11/17/2023 最后编辑:Fawfulcopter 更新时间:11/17/2023 访问量:57

问:

我是 JavaScript 的 Array.prototype.find() 的忠实粉丝。我喜欢能够只接受一个数组,给它抛出一个谓词,然后获取该数组中与谓词匹配的第一个元素。我希望在PHP中以辅助函数的形式实现该功能。

这是我想出的:

function find(array &$arr, callable $callable)
{
    foreach ($arr as &$x) {
        if ($callable($x)) {
            return $x;
        }
    }
    return null;
}

这很好用。主要。我很难获得我想要的关于引用的确切行为。也就是说,如果返回的元素是一个复杂的结构(即,它是一个对象或数组),并且我通过调用返回值的实例方法或键来修改该元素结构的内容,则更改应立即反映在我从中提取它的数组中。这是 JavaScript 的原生行为,我希望我的 PHP 端口完全模拟这一点。

如果正在迭代的数组充满了对象,则此操作完美无缺。PHP 默认传递对对象的引用,所以这不是问题。

当迭代数组充满嵌套子数组时,我的问题就出现了。如果我从这个函数得到一个子数组值,它就会被值传递并复制。如果调用方修改找到的子数组的副本,则更改不会反映在父数组中。

好的,所以,简单的修复,对吧?只需将 & 符号放在函数定义上即可。.容易。&find(...)

但这个函数的本质是,有时它找不到与谓词匹配的元素。所以它有时必须返回。当与按引用返回函数定义结合使用时,这会引发一个:NULLE_NOTICE

PHP 注意:在 [stacktrace] 中,只有变量引用应该通过引用返回

我可以通过在函数中定义一个 null 变量并返回对该变量的引用来阻止通知,但这感觉就像代码味道。简单地抑制通知也是如此。我宁愿不做其中任何一个,除非我能充分确信这是首选的成语。

显然我做了一些不正确的事情,但我不确定解决方案应该是什么。我在这里错过了更好的模式吗?

编辑:此假设函数的预期用法示例:

$main_array = [
    [ 'key' =>  10 ],
    [ /* stuff */ ],
    [ /* stuff */ ],
    // ...
];

// Raises E_NOTICE if null
$sub_array =& find(
    $main_array,
    fn($x) => $x['key'] === 10
);

if (!$sub_array) {
    // handle null case as appropriate
}

// Modify substructure of returned value
$sub_array['key'] = 20;

// Changed value should reflect in main array:
echo $main_array[0]['key'] === 20 ? 'True' : 'False';
// True
PHP 数组按 引用返回

评论

0赞 Adi 11/17/2023
this raises a notice你能添加通知吗?您也可以添加代码,在何处以及如何访问该函数。find()
0赞 Fawfulcopter 11/17/2023
我已经按照你的要求做了。

答:

1赞 IMSoP 11/17/2023 #1

显然我做错了什么

我认为这根本不明显:在 PHP 中,引用是变量之间的链接;因此,如果要使用引用,则必须使用变量。正如你已经发现的,这意味着要通过引用返回一个,你需要将一个变量设置为first。换言之,必须先使用值赋值 (),然后才能使用引用赋值 ()。nullnull$foo = null$bar =& $foo

默认情况下,PHP 传递对对象的引用

需要注意的是,这与引用分配的意义不同。请考虑以下代码:=&

$arr = [new class{}];
$foo = find($arr, fn($item) => true);
$foo->someProp = 'hello';
$foo = 42;
var_dump($arr);

该数组仍然包含原始对象,但该对象现在将在其属性中具有 。变量不是引用,但对象是可变结构。$arr'hello'$someProp

将第 2 行更改为 ,而是数组将包含值 ,并且对象将被销毁。变量本身是一个引用,是 的另一个名称,因此等价于$foo =& find(...42$arr[0]$foo = 42;$arr[0] = 42;

我在这里错过了更好的模式吗?

可以说,“更好”的模式就是不使用引用,这通常很令人困惑。如果需要可变值,请使用对象,而不是数组或其他类型。

请注意,JavaScript 行为不同的原因不是它默认使用类似于 PHP 引用的任何内容,而是因为在 JavaScript 中数组本身就是可变对象

将上面的示例翻译成 JavaScript:

arr = [new Object];
foo = arr.find((item) => true);
foo.someProp = 'hello';
foo = 42;
console.log(arr);

与 PHP 一样,仍然包含已更改的对象; 不被视为对数组元素的直接引用。arrfoo

评论

0赞 Fawfulcopter 11/17/2023
标记为已回答,因为您几乎回答了原始问题。不过,我不得不问:你是在暗示PHP数组是不可变的吗?他们看起来确实如此。它们不像 Python 的元组,你每次都必须创建一个新的元组。为什么在PHP中将数组作为函数传递出去时无法利用它?此外,如果 PHP 中的数组仅在从函数返回时才是可变的,为什么如果我通过引用返回它,它会突然变得可变?为什么 PHP 中数组的引用和可变性密不可分?
1赞 IMSoP 11/17/2023
@Fawfulcopter PHP 的数组不像对象那样“可变”,但它们的行为更像是字符串和整数。要查看差异,请考虑代码 - 该值未更改,并且仍保留该值;增量计算一个新值并将其分配给 。同样的情况是 - 数组没有突变,计算并分配一个新的数组值。请注意,实现可以对此进行优化,以避免不必要的内存复制(使用“写入时复制”),但从语义上讲,没有直接的突变。$a = 1; $b = $a; $b++;1$a$b$a = []; $b = $a; $b[] = 42;
0赞 IMSoP 11/17/2023
@Fawfulcopter 引用变得复杂的原因是 PHP 中的数组在某种意义上是变量数组,因此数组的每个元素本身都可以成为引用集的一部分,例如 with 或 .然后,对该数组元素的任何访问实际上是对共享值的访问,并且数组的任何新副本都会添加对该引用集的引用,而不是复制它当前指向的值。即使集合中只有一个引用,这种行为也会持续存在,例如,在引用确实令人困惑之后。$foo =& $arr[0];$arr[0] =& $foo;unset($foo);
0赞 Fawfulcopter 11/17/2023
我将您的示例理解为 PHP 创建一个新的内存克隆并将其提供给 .这在PHP文档中,我已经知道了。但是,令人困惑的是数组可变性的简单问题,例如.你可以以这种方式进行变异。如果数组在后台确实是不可变的,那么这意味着此操作将计算一个添加了新键的全新数组,丢弃旧值 ,并分配新值。你是说这就是PHP正在做的事情吗?因为那可以向我解释这一切。$a = []; $b = $a; $b[] = 42;$a$b$a['key'] = 'value'$a$a
1赞 IMSoP 11/17/2023
@Fawfulcopter 我试图传达的区别是你是在改变变量,还是在改变。如果你写,你不会说你正在“将 1 突变为 2”,你会说你正在“改变为值 1 而不是值 2”。同样,不会“改变空数组”,而是“更改为具有值而不是”。这是否真的涉及复制是一个实现细节,但从语义上讲,整数和数组的行为方式相同,而对象的行为方式不同。$a=1; $a++;$a$a=[]; $a['key'] = 'value';$a['key'=>'value'][]