[php-src] Issue #8156: Classes extending `IteratorAggregate` can not implement `RecursiveIterator`

From: 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 } ```

« previous php.bugs (#240084) next »