diff --git a/src/Breadcrumb.php b/src/Breadcrumb.php new file mode 100644 index 0000000..4684deb --- /dev/null +++ b/src/Breadcrumb.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chevere\VarSupport; + +use Chevere\VarSupport\Interfaces\BreadcrumbInterface; +use OutOfBoundsException; +use function Chevere\Message\message; + +final class Breadcrumb implements BreadcrumbInterface +{ + /** + * @var array + */ + private array $items = []; + + private int $pos = -1; + + private int $id = -1; + + public function __toString(): string + { + if ($this->items === []) { + return ''; + } + + $return = ''; + foreach ($this->items as $item) { + $return .= sprintf('[%s]', $item); + } + + return $return; + } + + public function toArray(): array + { + return $this->items; + } + + public function has(int $pos): bool + { + return array_key_exists($pos, $this->items); + } + + public function count(): int + { + return count($this->items); + } + + public function pos(): int + { + return $this->pos; + } + + public function withAdded(string $item): BreadcrumbInterface + { + $new = clone $this; + ++$new->id; + $new->items[$new->id] = $item; + $new->pos = $new->id; + + return $new; + } + + public function withRemoved(int $pos): BreadcrumbInterface + { + if (! array_key_exists($pos, $this->items)) { + throw new OutOfBoundsException( + (string) message( + 'Pos `%pos%` not found', + pos: $pos + ) + ); + } + $new = clone $this; + unset($new->items[$pos]); + + return $new; + } +} diff --git a/src/Interfaces/BreadcrumbInterface.php b/src/Interfaces/BreadcrumbInterface.php new file mode 100644 index 0000000..0d3d158 --- /dev/null +++ b/src/Interfaces/BreadcrumbInterface.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chevere\VarSupport\Interfaces; + +use Countable; +use Stringable; + +/** + * Describe a general purpose iterator companion which builds a breadcrumb + * path for nested variables, enabling to easily locate current position. + */ +interface BreadcrumbInterface extends Stringable, Countable +{ + /** + * Returns an string representation of the object. + * + * ```php + * return '[item0][item1][itemN]...[itemN+1]'; + * ``` + */ + public function __toString(): string; + + /** + * Returns an array representation of the object. + * + * ```php + * return [0 => 'item',]; + * ``` + * @return array + */ + public function toArray(): array; + + /** + * Indicates whether the instance has the given position. + */ + public function has(int $pos): bool; + + /** + * Returns the current breadcrumb position. + */ + public function pos(): int; + + /** + * Return an instance with the specified added item. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified added item. + */ + public function withAdded(string $item): self; + + /** + * Return an instance with the specified pos removed. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified pos removed. + */ + public function withRemoved(int $pos): self; +} diff --git a/src/ObjectVariable.php b/src/ObjectVariable.php index 906371f..d82eafc 100644 --- a/src/ObjectVariable.php +++ b/src/ObjectVariable.php @@ -13,9 +13,8 @@ namespace Chevere\VarSupport; -use Chevere\Iterator\Breadcrumb; -use Chevere\Iterator\Interfaces\BreadcrumbInterface; use Chevere\VarSupport\Exceptions\ObjectNotClonableException; +use Chevere\VarSupport\Interfaces\BreadcrumbInterface; use Chevere\VarSupport\Interfaces\ObjectVariableInterface; use Chevere\VarSupport\Traits\BreadcrumbIterableTrait; use ReflectionNamedType; diff --git a/src/StorableVariable.php b/src/StorableVariable.php index ecb6c55..3de7d63 100644 --- a/src/StorableVariable.php +++ b/src/StorableVariable.php @@ -13,9 +13,8 @@ namespace Chevere\VarSupport; -use Chevere\Iterator\Breadcrumb; -use Chevere\Iterator\Interfaces\BreadcrumbInterface; use Chevere\VarSupport\Exceptions\UnableToStoreException; +use Chevere\VarSupport\Interfaces\BreadcrumbInterface; use Chevere\VarSupport\Interfaces\StorableVariableInterface; use Chevere\VarSupport\Traits\BreadcrumbIterableTrait; use ReflectionNamedType; diff --git a/src/Traits/BreadcrumbIterableTrait.php b/src/Traits/BreadcrumbIterableTrait.php index becf26d..7da543d 100644 --- a/src/Traits/BreadcrumbIterableTrait.php +++ b/src/Traits/BreadcrumbIterableTrait.php @@ -13,8 +13,8 @@ namespace Chevere\VarSupport\Traits; -use Chevere\Iterator\Interfaces\BreadcrumbInterface; use Chevere\VarSupport\Exceptions\ObjectNotClonableException; +use Chevere\VarSupport\Interfaces\BreadcrumbInterface; use OutOfBoundsException; trait BreadcrumbIterableTrait diff --git a/tests/BreadcrumbTest.php b/tests/BreadcrumbTest.php new file mode 100644 index 0000000..884d909 --- /dev/null +++ b/tests/BreadcrumbTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chevere\Tests; + +use Chevere\VarSupport\Breadcrumb; +use OutOfBoundsException; +use PHPUnit\Framework\TestCase; + +final class BreadcrumbTest extends TestCase +{ + public function testConstruct(): void + { + $breadcrumb = new Breadcrumb(); + $this->assertEmpty($breadcrumb->toArray()); + $this->assertEmpty($breadcrumb->__toString()); + $this->assertCount(0, $breadcrumb); + $this->assertFalse($breadcrumb->has(0)); + $this->assertSame(-1, $breadcrumb->pos()); + } + + public function testWithAddedItems(): void + { + $items = [ + 'test-0', + 'test-1', + 'test-2', + ]; + $breadcrumb = new Breadcrumb(); + $withAdded = $breadcrumb; + foreach ($items as $pos => $item) { + $withAdded = $withAdded->withAdded($item); + $this->assertTrue($withAdded->has($pos)); + $this->assertSame($pos, $withAdded->pos()); + $this->assertContains($item, $withAdded->toArray()); + $this->assertStringContainsString($item, $withAdded->__toString()); + } + $this->assertNotSame($breadcrumb, $withAdded); + $this->assertSame($items, $withAdded->toArray()); + $this->assertSame( + '[' . implode('][', $items) . ']', + $withAdded->__toString() + ); + $withRemoved = $withAdded->withRemoved(1); + $this->assertNotSame($withAdded, $withRemoved); + $this->assertNotContains($items[1], $withRemoved->toArray()); + $this->assertStringNotContainsString($items[1], $withRemoved->__toString()); + } + + public function testWithRemovedItems(): void + { + $items = [ + 'test-0', + 'test-1', + 'test-2', + ]; + $breadcrumb = new Breadcrumb(); + $pos = 0; + foreach ($items as $pos => $item) { + $breadcrumb = $breadcrumb + ->withAdded($item) + ->withRemoved($pos); + $this->assertFalse($breadcrumb->has($pos)); + $this->assertNotContains($item, $breadcrumb->toArray()); + $this->assertStringNotContainsString($item, $breadcrumb->__toString()); + } + $this->assertCount(0, $breadcrumb); + $this->assertEmpty($breadcrumb->toArray()); + $this->assertEmpty($breadcrumb->__toString()); + $this->expectException(OutOfBoundsException::class); + $breadcrumb->withRemoved($pos); + } +}