[php-src] Issue #8156: Classes extending `IteratorAggregate` can not implement `RecursiveIterator`
| From: | ghostwriter | Date: | Mon, 28 Feb 2022 20:42:27 +0000 |
| Subject: | [php-src] Issue #8156: Classes extending `IteratorAggregate` can not implement `RecursiveIterator` | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-240084@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/8156
Comment Author: ghostwriter
First, thank you for taking the time to answer my questions. You're an amazing human being,
thank you. I've learned and corrected some of the things I originally misunderstood.
---
> Iterator and IteratorAggregate are two separate mechanisms for iteration so it logically does
> not make sense to support both.
Agreed.
> They aren't identical behaviors. They're both iterable but they go about it two
> completely different ways: one of them means the object modifies itself during iteration, and the
> other means the object remains unchanged because it uses a proxy object to handle iteration (which
> does gets modified).
I misspoke when I said "they are identical behaviors", I meant, **they're both
Traversable**.
``` php
interface Traversable {
}
interface IteratorAggregate extends Traversable {
/* Methods */
public getIterator(): Traversable
}
interface Iterator extends Traversable {
/* Methods */
public current(): mixed
public key(): mixed
public next(): void
public rewind(): void
public valid(): bool
}
```
My understanding was that, IteratorAggregate is an internal decorator/proxy
implementation of a Traversable that provides a/n Iterator&Traversable
to iterate over. By implementing IteratorAggregate you are implementing a proxy to
Iterator.
Is this correct?
If so, RecursiveIterator should accept IteratorAggregate as an
Iterator provider.
My question is why IteratorAggregate and RecursiveIterator can not be used
together.
* incorrect abstraction?
* incorrect implementation?
* bug?
>Fatal error: Class RecursiveFixedArrayProxy cannot implement both Iterator and IteratorAggregate
>at the same time <https://3v4l.org/usWZK>
``` php
<?php
declare(strict_types=1);
final class RecursiveFixedArrayProxy implements IteratorAggregate, RecursiveIterator
{
private SplFixedArray $splFixedArray;
public function __construct(iterable $iterable = [])
{
$this->splFixedArray = new SplFixedArray(
is_countable($iterable) ?
count($iterable) :
iterator_count($iterable)
);
$offset = 0;
$size = $this->splFixedArray->count();
foreach ($iterable as $key => $value) {
if($offset === $size){
// resize the SplFixedArray when nessary. (if the iterable passed in via the
__construct implements countable differently eg. where count() returns the capacity/size of the
iterable)
$this->splFixedArray->setSize(++$size);
}
if(is_array($value)){
// save arrays as RecursiveFixedArrayProxy
$this->splFixedArray->offsetSet($offset++, new self($value));
continue;
}
// save all other values as stdClass via Closure
$this->splFixedArray->offsetSet(
$offset++,
(static function(mixed $key = null, mixed $value = null): stdClass{
$stdClass = new stdClass();
$stdClass->key = $key;
$stdClass->value = $value;
return $stdClass;
})($key, $value)
);
}
$this->splFixedArray->setSize($offset);
}
// required from IteratorAggregate
public function getIterator(): Generator
{
// to provide a Recursive SplFixedArray implemtation.
yield from new RecursiveArrayIterator(
// is this wrong? trying to avoid using arrays, for memory boost
// avoids: $this->splFixedArray->toArray()
$this->splFixedArray
);
}
// required from RecursiveIterator
public function getChildren(): ?RecursiveIterator
{
/** @return RecursiveFixedArray */
return $this->current();
}
// required from RecursiveIterator
public function hasChildren(): bool
{
// traverse recursively if *current* is instance of RecursiveFixedArray
return $this->current() instanceof RecursiveFixedArray;
}
}
```
---
How can you recursively traverse a SplFixedArray (no modification), without having to
convert SplFixedArray to an array and relying on
RecursiveArrayIterator?
* extending SplFixedArray & RecursiveIterator causes error
* using a proxy that implements IteratorAggregate & RecursiveIterator
causes error
> there's no way to "un-implement" an interface.
we can however refactor the abstraction
``` php
// eg. Add interface
interface RecursiveTraversable extends Traversable {
/* Methods */
public getChildren(): ?RecursiveTraversable
public hasChildren(): bool
}
interface RecursiveIterator extends RecursiveTraversable {
/* Inherited methods */
public getChildren(): ?RecursiveIterator
public hasChildren(): bool
/* Inherited methods */
public Iterator::current(): mixed
public Iterator::key(): mixed
public Iterator::next(): void
public Iterator::rewind(): void
public Iterator::valid(): bool
}
// extends RecursiveTraversable instead of Iterator
interface RecursiveIteratorAggregate extends RecursiveTraversable {
/* Inherited methods */
public getChildren(): ?RecursiveIterator
public hasChildren(): bool
/* Methods */
public getIterator(): Traversable
}
```