Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion src/SegmentingContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -70,6 +75,11 @@ class SegmentingContainer implements ContainerInterface
*/
protected $delimiter;

/**
* @var Iterator
*/
protected $iterator;

/**
* Constructor.
*
Expand Down Expand Up @@ -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;
}
}
4 changes: 1 addition & 3 deletions tests/system/MultipleAccessTypesWithMapsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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'));
}
}
Expand Down