php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #71217 foreach() with inline assignment doesn't keep references
Submitted: 2015-12-24 20:07 UTC Modified: 2015-12-24 22:26 UTC
Votes:2
Avg. Score:4.5 ± 0.5
Reproduced:2 of 2 (100.0%)
Same Version:1 (50.0%)
Same OS:1 (50.0%)
From: bugs dot php dot net at ss dot st dot tc Assigned:
Status: Not a bug Package: Scripting Engine problem
PHP Version: 7.0.1 OS: Linux, OSX
Private report: No CVE-ID: None
 [2015-12-24 20:07 UTC] bugs dot php dot net at ss dot st dot tc
Description:
------------
When an inline assignment of array_expression is done right in foreach() statement, all further references to iterated array items seem to become their copies.

I believe both PHP 5.6 and 7 are affected, yet still they behave differently.
Tested on PHP 5.6.12, 5.6.16, 7.0.0, 7.0.1.


Test script:
---------------
<?php

# source array
$source = [ 'subarray' => ['item' => 'old value ---'] ];


# $ref1 is assigned to $source right in the foreach()
foreach ( $ref1 = $source as &$subarray ) {
	$subarray['item'] = 'NEW value';
	echo $subarray['item'], PHP_EOL;
	echo $ref1['subarray']['item'], PHP_EOL;
}
echo PHP_EOL;


# $ref2 is assigned to $source normally, as a separate operation
$ref2 = $source;
foreach ( $ref2 as &$subarray ) {
	$subarray['item'] = 'NEW value';
	echo $subarray['item'], PHP_EOL;
	echo $ref2['subarray']['item'], PHP_EOL;
}
echo PHP_EOL;


########################################

# similar to $source array, but implemented using a function
function getSource() {
	return [ 'subarray' => ['item' => 'old value ---'] ];
}


# $ref3 is assigned to result of getSource() right in the foreach()
foreach ( $ref3 = getSource() as &$subarray ) {
	$subarray['item'] = 'NEW value';
	echo $subarray['item'], PHP_EOL;
	echo $ref3['subarray']['item'], PHP_EOL;
}
echo PHP_EOL;


# $ref4 is a "normal" reference to a result of getSource()
$ref4 = getSource();
foreach ( $ref4 as &$subarray ) {
	$subarray['item'] = 'NEW value';
	echo $subarray['item'], PHP_EOL;
	echo $ref4['subarray']['item'], PHP_EOL;
}

Expected result:
----------------
both PHP 5 and 7:

NEW value
NEW value

NEW value
NEW value

NEW value
NEW value

NEW value
NEW value

Actual result:
--------------
PHP 7: 

NEW value
old value ---

NEW value
NEW value

NEW value
old value ---

NEW value
NEW value


PHP 5:

NEW value
old value ---

NEW value
NEW value

NEW value
NEW value

NEW value
NEW value

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-12-24 20:26 UTC] php at etc dot chkgo dot com
Faced the same problem in a part of function results assignment right in foreach and changing iterated items by reference. Got unexpected behavior on PHP 7.0.0 and 7.0.1 but seemed working fine on 5.6. /Gentoo

So it was pretty much as described in the ticket. My vote.
 [2015-12-24 20:53 UTC] inefedor at gmail dot com
There's a few kinds of expressions from which you can meaningfully take a reference: it's `$var` (just calling a variable by name), `$var->prop`, `$var["key"]` and probably some flavors of variable variables (e.g. `$$var`).

But assignment is not an expression that could be used as a reference. Now in 5.x it worked, probably by mistake and it would be a BC break to remove it from 5.x. But it was fixed in PHP 7.
 [2015-12-24 21:21 UTC] php at etc dot chkgo dot com
@inefedor Well, buildding such a constriction, I expect that assignment would be made before the loop starts. As for the reference, I expect it to be made to the items I iterate, not the assignment. Assignment itself can't be iterated. So if foreach is provided with unacceptable argument, why not throw an error then?
 [2015-12-24 21:55 UTC] bugs dot php dot net at ss dot st dot tc
I see @inefedor's point. Turns out we've been using this bug for ages (as a nice "feature"). The worst thing here is that such constructions are spread all over our codebase, especially case #3.

But I strongly agree with another expressed opinion here: I'd also expect leftmost part of an assignment to act as a result of an assignment operation (and therefore to be iterated, instead of its copy (or whatever is a result of assignment operation)).
 [2015-12-24 22:26 UTC] nikic@php.net
-Status: Open +Status: Not a bug
 [2015-12-24 22:26 UTC] nikic@php.net
As pointed out by inefedor, the previous behavior was a bug and the new behavior is correct. Simple assignment expressions return *values*, not references, so you'll be operating on a copy.

The only type of assignment expression that returns a reference is a by-reference assignment. If you iterate other something like $ref =& $source, you will iterate over a reference to $ref and $source.

As to the suggestion that an error shall be thrown: This is not done for the same reason why temporary expressions were allowed as arguments for by-ref foreach in the first place in PHP 5.5: By-reference iteration can be meaningful for non-reference expressions if a) the expression is an Iterator supporting by-reference iteration, b) the expression is a simple object or c) the expression is an array containing reference elements. In all of these cases modifications performed in a by-ref foreach loop can be observable.
 
PHP Copyright © 2001-2026 The PHP Group
All rights reserved.
Last updated: Wed Oct 07 15:00:01 2026 UTC