I have a couple of queries about modifying an array during a foreach() loop. In the code below I loop through three arrays that contain closures/callbacks and invoke each one. I append a closure to the end of each array during iteration, however sometimes foreach() doesn't seem to recognise that the array has changed size and so the appended closure doesn't get called.
class Foo
{
private $a1 = array();
private $a2 = array();
public function f()
{
echo '<pre style="font-size: 20px;">';
echo 'PHP: ' . phpversion() . '<br><br>';
$this->a1[] = function() { echo 'a1 '; };
$this->a1[] = array($this, 'g');
foreach ($this->a1 as &$v)
{
// The callback added in g() never gets called.
call_user_func($v);
//echo 'count(v) = ' . count($v) . ' ';
}
echo '<br>';
// The same thing works fine with a for() loop.
$this->a2[] = function() { echo 'a2 '; };
$this->a2[] = array($this, 'h');
for ($i = 0; $i < count($this->a2); ++$i)
call_user_func($this->a2[$i]);
echo '<br>';
// It also works fine using a local array as long as it
// starts off with more than one element.
$a3[] = function() { echo 'a3 '; };
//$a3[] = function() { echo 'a3 '; };
$i = 0;
foreach ($a3 as &$x)
{
call_user_func($x);
if ($i++ > 1) // prevent infinite loop
break;
// Why does this get called only if $a3 originally starts
// with more than one element?
$a3[] = function() { echo 'callback '; };
}
echo '</pre>';
}
private function g()
{
echo 'g() ';
$this->a1[] = function() { echo 'callback '; };
}
private function h()
{
echo 'h() ';
$this->a2[] = function() { echo 'callback '; };
}
}
$foo = new Foo;
$foo->f();
Output:
PHP: 5.3.14-1~dotdeb.0
a1 g()
a2 h() callback
a3
Expected output:
a1 g() callback
a2 h() callback
a3 callback
Output for $a3 if I uncomment the second element before the loop:
a3 a3 callback
- Why doesn't the first loop
foreach ($this->a1 as &$v)realise$vhas another element to iterate over? - Why does modifying
$a3work during the third loopforeach ($a3 as &$x), but only when the array starts off with more than one element?
I realise modifying an array during iteration is probably not a good idea, but since PHP seems to allow it I'm curious why the above works the way it does.
while (list(, $value) = each($arr))is supposed to be exactly the same as a foreach, however I had no* problems when using the while loop instead of the foreach.foreachwas designed to copy the array and iterate over it, because you don't run into infinite loops that way. as Rasmus Lerdorf puts it: "Ugly problems often require ugly solutions. Solving an ugly problem in a pure manner is bloody hard.". The "elegant" solution would likely be using a callback function rather than relying on the array.