Daniel W.
Daniel W.

Reputation: 32340

SplObjectStorage - detach behaviour

I found this comment on the PHP doc page and was suprised about it.

His comment might not be the best, but I wonder why the output of the following script is "left in the storage: 2"? And why "Outputs:" does not show up "2".

I expect the storage to be empty after detaching all objects.

<?php
class A {
    public $i;
    public function __construct($i) {
        $this->i = $i;
    }
}

$container = new \SplObjectStorage();

$container->attach(new A(1));
$container->attach(new A(2));
$container->attach(new A(3));
$container->attach(new A(4));
$container->attach(new A(5));

foreach ($container as $item) {
    echo $item->i . "\n";
    $container->detach($item);
}
echo "== Left in storage ==\n";
foreach ($container as $item) {
    echo $item->i . "\n";
}

/* Outputs:
1
3
4
5
== Left in storage ==
2
*/

Upvotes: 3

Views: 544

Answers (1)

Jeff Lambert
Jeff Lambert

Reputation: 24661

This is because by detaching an object inside of a foreach loop, this prevents SplObjectStorage::next from working correctly. I was able to find this information in this user contribited note in the PHP docs (so take that for what it's worth).

If you want to detach objects during iterations, you should dereference objects, before you call next() and detach the reference after next()

There's also a bug report available on this, and apparently the reason it doesn't work is because, while detaching, the method rewinds the container's internal array pointer which is what causes havoc while detaching inside of a loop.

In your case, following that note, you can change the loop removing objects from the storage container like this and it should work as expected:

$container->attach(new A(1));
$container->attach(new A(2));
$container->attach(new A(3));
$container->attach(new A(4));
$container->attach(new A(5));

$container->rewind();
while ($container->valid()) {
    $item = $container->current();
    echo $item->i . "\n";
    $container->next();
    $container->detach($item);
}
/* Outputs:
1
2
3
4
5
== Left in storage ==
*/

Upvotes: 4

Related Questions