PHP: References and Loops

The PHP language has a couple of quirky behaviours which can catch you out unexpectedly if you don't know what to look for. Here are two I've discovered recently:

1. all variables that contain instantiated objects are references to that object, and explicit "references" actually refer to ... er, the variable (symbol) table, or something ..?

The only way I can explain is by means of example:

<?php
class SimpleClass {
     function __construct() {
           $this->var = '';
     }
}

$instance = new SimpleClass(); // 
$assigned = $instance;         // copy assignment
$reference =& $instance;       // reference assignment

$instance->var = 'asdasd';     // change the field in the original instance
$reference = null;             // make the 'reference' variable point to nothing

var_dump($reference);          // => NULL, as expected
var_dump($assigned);           // => object(SimpleClass)#1 (1) { ["var"] => string(6) "asdasd" }
var_dump($instance);           // => NULL

Two things to take away:

  1. $assigned = $instance creates a second variable which refers to the original object; and
  2. $reference =& $instance essentially means that $reference is now an alias for $instance, and anything you do to one also happens to the other. I don't know any other language that has these sorts of references.

If you change $reference = null to $instance = null the final output is the same. $reference and $instance are the same variable (albeit spelled differently.)

2. foreach maintains a reference to an array, even if you overwrite the array variable inside the loop.

<?php
$foo = array(1,2,3);
foreach ($foo as $x) {
	echo $x;
	$foo = array(4,5,6);
}
var_dump($foo);

...which outputs:

1
2
3
array(3) {
  [0] => int(4)
  [1] => int(5)
  [2] => int(6)
}

While you're inside the loop, the iterator always refers to the object originally identified by the variable.

For a more potentially destructive example, we can overwrite $foo with something that can't be foreached over:

<?php
$foo = array(1,2,3);
foreach ($foo as $x) {
	echo $x;
	$foo = $x;
}
var_dump($foo);

...which outputs:

1
2
3
int(3)

And now for something that will hopefully make you cringe:

<?php
$foo = array(1,2,3);
foreach ($foo as $foo) {
	echo $foo;
}
var_dump($foo);

Yes, apparently this is valid, even though we're overwriting the array with its own members in a horribly obfuscating and mind-hurting way. The output is:

1
2
3
int(3)

...which I suppose makes sense, once you realise that the foreach loop seems to have created a safe reference to the original $foo array. Note that the second $foo variable doesn't mask or shadow the outer-scope's foo, it overwrites it, just as if you did the assignment inside the loop body.


Matthew Kerwin

Published
Modified
License
CC BY-SA 4.0
Tags
gotcha, php, software

Comments powered by Disqus