diff --git a/src/SegmentingContainer.php b/src/SegmentingContainer.php index bf21b40..a406430 100644 --- a/src/SegmentingContainer.php +++ b/src/SegmentingContainer.php @@ -5,7 +5,12 @@ namespace Dhii\Container; use Dhii\Collection\ContainerInterface; +use Exception; +use Iterator; +use IteratorAggregate; use Psr\Container\ContainerInterface as PsrContainerInterface; +use Traversable; +use UnexpectedValueException; use function array_filter; use function ltrim; @@ -53,7 +58,7 @@ * @since [*next-version*] * @see PathContainer For an implementation that achieves the opposite effect. */ -class SegmentingContainer implements ContainerInterface +class SegmentingContainer implements ContainerInterface, Iterator { /** * @var PsrContainerInterface @@ -70,6 +75,11 @@ class SegmentingContainer implements ContainerInterface */ protected $delimiter; + /** + * @var Iterator + */ + protected $iterator; + /** * Constructor. * @@ -116,4 +126,73 @@ public function has($key) { return $this->inner->has($key); } + + public function current() + { + return $this->iterator->current(); + } + + public function next(): void + { + $this->iterator->next(); + } + + public function key() + { + return $this->iterator->key(); + } + + public function valid() + { + return $this->startsWith($this->iterator->key(), $this->root); + } + + public function rewind(): void + { + if (!($this->inner instanceof Traversable)) { + throw new UnexpectedValueException('Cannot rewind: inner container not an iterator'); + } + + $this->iterator = $this->normalizeIterator($this->inner); + $this->iterator->rewind(); + } + + /** + * Normalizes a traversable into an iterator. + * + * This is helpful because an `Iterator` can be iterated over by explicitly calling known methods, + * which include those that expose the current key. + * + * @param Traversable $traversable The traversable to normalize. + * + * @return Iterator The normalized iterator. + * + * @throws Exception If problem normalizing. + */ + protected function normalizeIterator(Traversable $traversable): Iterator + { + if ($traversable instanceof Iterator) { + return $traversable; + } + + // Any `Traversable` that is not an `Iterator` is an `IteratorAggregate` + assert($traversable instanceof IteratorAggregate); + $traversable = $traversable->getIterator(); + + return $this->normalizeIterator($traversable); + } + + /** + * Determines whether the subject string has another string at the start. + * + * @param string $subject The subject to check. + * @param string $start The start string to check for. + * + * @return bool True if subject starts with specified start string; false otherwise. + */ + protected function startsWith(string $subject, string $start): bool + { + $length = strlen($start); + return substr($subject, 0, $length) === $start; + } } diff --git a/tests/system/MultipleAccessTypesWithMapsTest.php b/tests/system/MultipleAccessTypesWithMapsTest.php index d54ade7..3149cee 100644 --- a/tests/system/MultipleAccessTypesWithMapsTest.php +++ b/tests/system/MultipleAccessTypesWithMapsTest.php @@ -8,7 +8,7 @@ use Dhii\Container\SegmentingContainer; use PHPUnit\Framework\TestCase; -class MultipleAccessTypesWithMaps__ extends TestCase +class MultipleAccessTypesWithMaps extends TestCase { const DEV_DB_HOST = 'localhost'; const STAGING_DB_HOST = '123.staging.myhost'; @@ -85,8 +85,6 @@ public function testPreserveIterabilityInNamespaced(array $devData, array $stagi } { - $this->markTestIncomplete('This will fail, because the segmenting container is not iterable, ' . - 'and will return instances of itself for keys that are not found.'); $this->assertIsIterable($container->get('staging')->get('db')); } }