Bug #80826 [Nab]: Array member reference affecting pass by value assignment

From: Date: Thu, 04 Mar 2021 09:26:33 +0000
Subject: Bug #80826 [Nab]: Array member reference affecting pass by value assignment
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-232530@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=80826&edit=1 ID: 80826 Updated by: requinix@php.net Reported by: tshumbeo at mailhouse dot biz Summary: Array member reference affecting pass by value assignment Status: Not a bug Type: Bug Package: *General Issues Operating System: Windows & Linux PHP Version: 7.4.15 Block user comment: N Private report: N New Comment: > This can happen without foreach: Correct. The common misconception about references is that references are "to" variables, and if you have $x=&$y that means $x refers "to" $y. But that's not true. The short version: a PHP variable doesn't contain a value itself, but rather the value is a separate thing and the variable knows which value it should use. $y=1 means there is a value int(1) and that the "y" variable knows about it. ("y" is like a C pointer.) When you create a reference with code like $x=&$y, what happens is that a new variable "x" is created knowing the same underlying value that "y" had, while a regular assignment would create the variable and also a whole new underlying value for it*. So a "reference" is what PHP calls a variable whose value is being used in other places too. > Perhaps this case should be mentioned not only in foreach's documentation... Have you seen the manual's section on references? https://www.php.net/manual/en/language.references.php (* actually it doesn't because of copy-on-write, but that's not important here) Previous Comments: ------------------------------------------------------------------------ [2021-03-04 08:49:42] tshumbeo at mailhouse dot biz > And when $a is copied into the f() function, its $a[2] is a copy *of the reference*. I understand now. Thanks. This can happen without foreach: <?php $a = [1, 2, 3]; $a[1] = &$x; // $a[1] is &NULL f($a); // $a[1] is now &array(NULL) ?> Perhaps this case should be mentioned not only in foreach's documentation (where it's not obvious at all) but in one on parameter passing or references? This doesn't have anything to do with foreach after all. ------------------------------------------------------------------------ [2021-03-04 08:28:42] requinix@php.net > In other words, there is an implicit $reference = <next array member>; > assignment in loop 2. No. You missed an important part in the note's first sentence: > Reference of a $value and the last array element remain even after the foreach > loop. *The last element is also a reference*. In your code, that means $reference *and* $a[2] are references. And when $a is copied into the f() function, its $a[2] is a copy *of the reference*. ------------------------------------------------------------------------ [2021-03-04 07:36:41] tshumbeo at mailhouse dot biz Please read the test script carefully. The documentation says about this case: foreach ($array as &$reference) { ... } // followed by: foreach ($array as $reference) { ... } In other words, there is an implicit $reference = <next array member>; assignment in loop 2. However, in my case there is no such assignment: the array is passed into the function via parameter that IS NOT a reference: foreach ($array as &$reference) { ... } f($array); Moreover, inside f() there are no references either and yet the outer $reference somehow affects how f() assigns array members (which must be a copy!). How can you explain this? ------------------------------------------------------------------------ [2021-03-03 21:00:00] requinix@php.net There is a big red note in foreach's documentation about this. ------------------------------------------------------------------------ [2021-03-03 19:46:30] tshumbeo at mailhouse dot biz Description: ------------ See the test script. When (1) is not present (leaving the member reference in the outside scope when f() is called), the assignment inside f() is able to modify the passed array even though it doesn't accept it by reference (note "&array(1)" in Actual Result). Removing the reference explicitly with (1) avoids this problem. Test script: --------------- <?php $a = [1, 2, 3]; foreach ($a as &$reference) { $reference = (object) ['x' => $reference]; } // (1) //unset($reference); f($a); var_dump($a); function f($a) { foreach ($a as $k => $v) { $a[$k] = [$v]; } } Expected result: ---------------- array(3) { [0]=> object(stdClass)#1 (1) { ["x"]=> int(1) } [1]=> object(stdClass)#2 (1) { ["x"]=> int(2) } [2]=> object(stdClass)#3 (1) { ["x"]=> int(3) } } Actual result: -------------- array(3) { [0]=> object(stdClass)#1 (1) { ["x"]=> int(1) } [1]=> object(stdClass)#2 (1) { ["x"]=> int(2) } [2]=> &array(1) { // !!! [0]=> object(stdClass)#3 (1) { ["x"]=> int(3) } } } ------------------------------------------------------------------------ -- Edit this bug report at https://bugs.php.net/bug.php?id=80826&edit=1

« previous php.bugs (#232530) next »