vendor/doctrine/mongodb-odm/lib/Doctrine/ODM/MongoDB/Iterator/CachingIterator.php line 31

  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ODM\MongoDB\Iterator;
  4. use Countable;
  5. use Generator;
  6. use ReturnTypeWillChange;
  7. use RuntimeException;
  8. use Traversable;
  9. use function count;
  10. use function current;
  11. use function key;
  12. use function next;
  13. use function reset;
  14. /**
  15.  * Iterator for wrapping a Traversable and caching its results.
  16.  *
  17.  * By caching results, this iterators allows a Traversable to be counted and
  18.  * rewound multiple times, even if the wrapped object does not natively support
  19.  * those operations (e.g. MongoDB\Driver\Cursor).
  20.  *
  21.  * @internal
  22.  *
  23.  * @template TValue
  24.  * @template-implements Iterator<TValue>
  25.  */
  26. final class CachingIterator implements CountableIterator
  27. {
  28.     /** @var array<mixed, TValue> */
  29.     private array $items = [];
  30.     /** @var Generator<mixed, TValue>|null */
  31.     private ?Generator $iterator;
  32.     private bool $iteratorAdvanced false;
  33.     private bool $iteratorExhausted false;
  34.     /**
  35.      * Initialize the iterator and stores the first item in the cache. This
  36.      * effectively rewinds the Traversable and the wrapping Generator, which
  37.      * will execute up to its first yield statement. Additionally, this mimics
  38.      * behavior of the SPL iterators and allows users to omit an explicit call
  39.      * to rewind() before using the other methods.
  40.      *
  41.      * @param Traversable<mixed, TValue> $iterator
  42.      */
  43.     public function __construct(Traversable $iterator)
  44.     {
  45.         $this->iterator $this->wrapTraversable($iterator);
  46.         $this->storeCurrentItem();
  47.     }
  48.     /** @see https://php.net/countable.count */
  49.     public function count(): int
  50.     {
  51.         $this->exhaustIterator();
  52.         return count($this->items);
  53.     }
  54.     public function __destruct()
  55.     {
  56.         $this->iterator null;
  57.     }
  58.     public function toArray(): array
  59.     {
  60.         $this->exhaustIterator();
  61.         return $this->items;
  62.     }
  63.     /** @return TValue|false */
  64.     #[ReturnTypeWillChange]
  65.     public function current()
  66.     {
  67.         return current($this->items);
  68.     }
  69.     /** @return mixed */
  70.     #[ReturnTypeWillChange]
  71.     public function key()
  72.     {
  73.         return key($this->items);
  74.     }
  75.     /** @see http://php.net/iterator.next */
  76.     public function next(): void
  77.     {
  78.         if (! $this->iteratorExhausted) {
  79.             $this->getIterator()->next();
  80.             $this->storeCurrentItem();
  81.         }
  82.         next($this->items);
  83.     }
  84.     /** @see http://php.net/iterator.rewind */
  85.     public function rewind(): void
  86.     {
  87.         /* If the iterator has advanced, exhaust it now so that future iteration
  88.          * can rely on the cache.
  89.          */
  90.         if ($this->iteratorAdvanced) {
  91.             $this->exhaustIterator();
  92.         }
  93.         reset($this->items);
  94.     }
  95.     /** @see http://php.net/iterator.valid */
  96.     public function valid(): bool
  97.     {
  98.         return $this->key() !== null;
  99.     }
  100.     /**
  101.      * Ensures that the inner iterator is fully consumed and cached.
  102.      */
  103.     private function exhaustIterator(): void
  104.     {
  105.         while (! $this->iteratorExhausted) {
  106.             $this->next();
  107.         }
  108.         $this->iterator null;
  109.     }
  110.     /** @return Generator<mixed, TValue> */
  111.     private function getIterator(): Generator
  112.     {
  113.         if ($this->iterator === null) {
  114.             throw new RuntimeException('Iterator has already been destroyed');
  115.         }
  116.         return $this->iterator;
  117.     }
  118.     /**
  119.      * Stores the current item in the cache.
  120.      */
  121.     private function storeCurrentItem(): void
  122.     {
  123.         $key $this->getIterator()->key();
  124.         if ($key === null) {
  125.             return;
  126.         }
  127.         $this->items[$key] = $this->getIterator()->current();
  128.     }
  129.     /**
  130.      * @param Traversable<mixed, TValue> $traversable
  131.      *
  132.      * @return Generator<mixed, TValue>
  133.      */
  134.     private function wrapTraversable(Traversable $traversable): Generator
  135.     {
  136.         foreach ($traversable as $key => $value) {
  137.             yield $key => $value;
  138.             $this->iteratorAdvanced true;
  139.         }
  140.         $this->iteratorExhausted true;
  141.     }
  142. }