Double Reference Variable Assignments in PHP

I just got through tracking down a very pernicious bug.

The problem was that an array assigned to an object was retaining its original link after cloning.  The original array was created with its values being assigned by reference.  This wouldn’t have been a problem in and of itself, but additionally there was a second array being created with a reference to the same variable.  This was both unnecessary and certain death to my script.

The takeaway from this is to avoid unnecessary variable assignment by reference. The original code was written over two years ago.

If I recall correctly my justification for using references was to save memory.

This is wrong.

Don’t think about references in a context of memory consumption (especially since PHP allocates on write), think about references in a context of “How do I need to treat this data?”

Here is the full script that is broken. Notice in the foreach two referential assignments.

$in = array( 'a' => '123', 'b' => '456' );
$vars = array();

foreach( $in as $key => $val )
{
	$_GET[$key] =& $val;
	$vars[] =& $val;
	unset( $val );
}

class test
{
	protected $_data;

	public function set( $data )
	{
		$this->_data = $data;
	}

	public function setVar( $key, $val )
	{
		$this->_data[$key] = $val;
	}
}

header( 'content-type: text/plain' );

echo 'Should be 123 and 456' . PHP_EOL;
var_dump( $_GET );

$test = new test();
$test->set( $_GET );

echo 'Should be 123 and 456' . PHP_EOL;
var_dump( $_GET );

$second = clone $test;
$second->setVar( 'a', 'abc' );

echo 'Should be 123 and 456' . PHP_EOL;
var_dump( $_GET );

Output

Should be 123 and 456
array(2) {
  ["a"]=>
  &string(3) "123"
  ["b"]=>
  &string(3) "456"
}
Should be 123 and 456
array(2) {
  ["a"]=>
  &string(3) "123"
  ["b"]=>
  &string(3) "456"
}
Should be 123 and 456
array(2) {
  ["a"]=>
  &string(3) "abc"
  ["b"]=>
  &string(3) "456"
}

Corrected Script

$in = array( 'a' => '123', 'b' => '456' );

foreach( $in as $key => $val )
{
	$_GET[$key] = $val;
}

class test
{
	protected $_data;

	public function set( $data )
	{
		$this->_data = $data;
	}

	public function setVar( $key, $val )
	{
		$this->_data[$key] = $val;
	}
}

header( 'content-type: text/plain' );

echo 'Should be 123 and 456' . PHP_EOL;
var_dump( $_GET );

$test = new test();
$test->set( $_GET );

echo 'Should be 123 and 456' . PHP_EOL;
var_dump( $_GET );

$second = clone $test;
$second->setVar( 'a', 'abc' );

echo 'Should be 123 and 456' . PHP_EOL;
var_dump( $_GET );

Correct Output

Should be 123 and 456
array(2) {
  ["a"]=>
  string(3) "123"
  ["b"]=>
  string(3) "456"
}
Should be 123 and 456
array(2) {
  ["a"]=>
  string(3) "123"
  ["b"]=>
  string(3) "456"
}
Should be 123 and 456
array(2) {
  ["a"]=>
  string(3) "123"
  ["b"]=>
  string(3) "456"
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s