diff --git a/src/Interfaces/ArrayViewInterface.php b/src/Interfaces/ArrayViewInterface.php index 636c0c9..0d5ca64 100644 --- a/src/Interfaces/ArrayViewInterface.php +++ b/src/Interfaces/ArrayViewInterface.php @@ -18,6 +18,13 @@ interface ArrayViewInterface extends \ArrayAccess, \IteratorAggregate, \Countabl */ public static function toView(&$source, ?bool $readonly = null): ArrayViewInterface; + /** + * @param array|ArrayViewInterface $source + * @param bool|null $readonly + * @return ArrayViewInterface + */ + public static function toUnlinkedView($source, ?bool $readonly = null): ArrayViewInterface; + /** * @return array */ diff --git a/src/Selectors/IndexListSelector.php b/src/Selectors/IndexListSelector.php index 3516fe7..4773739 100644 --- a/src/Selectors/IndexListSelector.php +++ b/src/Selectors/IndexListSelector.php @@ -14,11 +14,11 @@ final class IndexListSelector implements ArraySelectorInterface private array $value; /** - * @param array $value + * @param array|ArrayViewInterface $value */ - public function __construct(array $value) + public function __construct($value) { - $this->value = $value; + $this->value = \is_array($value) ? $value : $value->toArray(); } /** diff --git a/src/Selectors/MaskSelector.php b/src/Selectors/MaskSelector.php index 5595879..e1be6c4 100644 --- a/src/Selectors/MaskSelector.php +++ b/src/Selectors/MaskSelector.php @@ -11,14 +11,14 @@ class MaskSelector implements ArraySelectorInterface /** * @var array */ - private array $value; + private $value; /** - * @param array $value + * @param array|ArrayViewInterface $value */ - public function __construct(array $value) + public function __construct($value) { - $this->value = $value; + $this->value = \is_array($value) ? $value : $value->toArray(); } /** diff --git a/src/Views/ArrayView.php b/src/Views/ArrayView.php index 15f327a..bd620e4 100644 --- a/src/Views/ArrayView.php +++ b/src/Views/ArrayView.php @@ -53,6 +53,14 @@ public static function toView(&$source, ?bool $readonly = null): ArrayViewInterf return $source; } + /** + * {@inheritDoc} + */ + public static function toUnlinkedView($source, ?bool $readonly = null): ArrayViewInterface + { + return static::toView($source, $readonly); + } + /** * @param array|ArrayViewInterface $source * @param bool|null $readonly diff --git a/tests/unit/ArrayView/ReadTest.php b/tests/unit/ArrayView/ReadTest.php new file mode 100644 index 0000000..91c6aff --- /dev/null +++ b/tests/unit/ArrayView/ReadTest.php @@ -0,0 +1,177 @@ + $value) { + $actual = $view[$i]; + $actualByStringIndex = $view[strval($i)]; + $expected = $source[$i]; + + $this->assertSame($expected, $actual); + $this->assertSame($expected, $actualByStringIndex); + } + + $this->assertSame($source, $view->toArray()); + $this->assertSame($source, [...$view]); + } + + /** + * @dataProvider dataProviderForReadCombine + */ + public function testReadCombined(array $source, callable $viewGetter, array $expected) + { + $view = $viewGetter($source); + + $this->assertSame($view->toArray(), $expected); + } + + public function dataProviderForArrayRead(): array + { + return [ + [[1]], + [[1, 2]], + [[1, 2, 3]], + [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]], + [[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10]], + ]; + } + + public function dataProviderForReadCombine(): array + { + return [ + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview('::2'), + [1, 3, 5, 7, 9], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => (new ArrayIndexListView($source, array_keys($source))) + ->subview('::2'), + [1, 3, 5, 7, 9], + ], + + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => (new ArrayIndexListView($source, array_keys($source))) + ->subview('::2'), + [1, 3, 5, 7, 9], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => (new ArrayMaskView( + $source, + [true, true, true, true, true, true, true, true, true, true] + ))->subview('::2'), + [1, 3, 5, 7, 9], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => (new ArraySliceView($source, new SliceSelector("::1"))) + ->subview('::2'), + [1, 3, 5, 7, 9], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview('::2') + ->subview(new MaskSelector([true, false, true, false, true])), + [1, 5, 9], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview('::2') + ->subview(new MaskSelector([true, false, true, false, true])) + ->subview(new IndexListSelector([0, 2])), + [1, 9], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview('::2') + ->subview(new MaskSelector([true, false, true, false, true])) + ->subview(new IndexListSelector([0, 2])), + [1, 9], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview('::2') + ->subview(new MaskSelector(ArrayView::toUnlinkedView([true, false, true, false, true]))) + ->subview(new IndexListSelector(ArrayView::toUnlinkedView([0, 2]))), + [1, 9], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview('::2') + ->subview(new MaskSelector([true, false, true, false, true])) + ->subview(new IndexListSelector([0, 2])) + ->subview('1:'), + [9], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview(new MaskSelector([true, false, true, false, true, false, true, false, true, false])) + ->subview(new MaskSelector([true, false, true, false, true])) + ->subview(new MaskSelector([true, false, true])) + ->subview(new MaskSelector([false, true])), + [9], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview(new MaskSelector([true, false, true, false, true, false, true, false, true, false])) + ->subview(new MaskSelector([true, false, true, false, true])) + ->subview(new MaskSelector([true, false, true])), + [1, 9], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview(new IndexListSelector([0, 2, 4, 6, 8])) + ->subview(new IndexListSelector([0, 2, 4])) + ->subview(new IndexListSelector([0, 2])) + ->subview(new IndexListSelector([1])), + [9], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview('::2') + ->subview('::2') + ->subview('::2'), + [1, 9], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview('::2') + ->subview('::2') + ->subview('::2') + ->subview('1:'), + [9], + ], + ]; + } +} diff --git a/tests/unit/ArrayView/WriteTest.php b/tests/unit/ArrayView/WriteTest.php new file mode 100644 index 0000000..350dd57 --- /dev/null +++ b/tests/unit/ArrayView/WriteTest.php @@ -0,0 +1,260 @@ + $value) { + $view[$i] = $toWrite[$i]; + + $this->assertSame($toWrite[$i], $view[$i]); + $this->assertSame($toWrite[$i], $source[$i]); + } + + $this->assertSame($toWrite, $view->toArray()); + $this->assertSame($toWrite, [...$view]); + $this->assertSame($toWrite, $source); + } + + /** + * @dataProvider dataProviderForArrayWrite + */ + public function testWriteArrayBySet(array $source, array $toWrite) + { + $view = ArrayView::toView($source); + + $view->set($toWrite); + + $this->assertSame($toWrite, $view->toArray()); + $this->assertSame($toWrite, [...$view]); + $this->assertSame($toWrite, $source); + } + + /** + * @dataProvider dataProviderForArrayWrite + */ + public function testWriteArrayBySlice(array $source, array $toWrite) + { + $view = ArrayView::toView($source); + + $view[':'] = $toWrite; + + $this->assertSame($toWrite, $view->toArray()); + $this->assertSame($toWrite, [...$view]); + $this->assertSame($toWrite, $source); + } + + /** + * @dataProvider dataProviderForSingleWrite + */ + public function testWriteSingleBySet(array $source, $toWrite, array $expected) + { + $view = ArrayView::toView($source); + + $view->set($toWrite); + + $this->assertSame($expected, $view->toArray()); + $this->assertSame($expected, [...$view]); + $this->assertSame($expected, $source); + } + + /** + * @dataProvider dataProviderForSingleWrite + */ + public function testWriteSingleBySlice(array $source, $toWrite, array $expected) + { + $view = ArrayView::toView($source); + + $view[':'] = $toWrite; + + $this->assertSame($expected, $view->toArray()); + $this->assertSame($expected, [...$view]); + $this->assertSame($expected, $source); + } + + /** + * @dataProvider dataProviderForIncrement + */ + public function testIncrement(array $source, array $expected) + { + $view = ArrayView::toView($source); + + foreach ($source as $i => $value) { + $view[$i] += 1; + + $this->assertSame($expected[$i], $source[$i]); + $this->assertSame($expected[$i], $view[$i]); + } + + $this->assertSame($expected, $view->toArray()); + $this->assertSame($expected, [...$view]); + $this->assertSame($expected, $source); + } + + /** + * @dataProvider dataProviderForWriteCombine + */ + public function testWriteBySet(array $source, callable $viewGetter, $toWrite, array $expected) + { + $view = $viewGetter($source); + + $view->set($toWrite); + + $this->assertSame($expected, $source); + } + + /** + * @dataProvider dataProviderForWriteCombine + */ + public function testWriteBySlice(array $source, callable $viewGetter, $toWrite, array $expected) + { + $view = $viewGetter($source); + + $view[':'] = $toWrite; + + $this->assertSame($expected, $source); + } + + public function dataProviderForArrayWrite(): array + { + return [ + [[1], [0]], + [[1, 2], [3, 5]], + [[1, 2, 3], [11, 22, 33]], + ]; + } + + public function dataProviderForSingleWrite(): array + { + return [ + [[1], 1, [1]], + [[1, 2], 2, [2, 2]], + [[1, 2, 3], 33, [33, 33, 33]], + ]; + } + + public function dataProviderForIncrement(): array + { + return [ + [[1], [2]], + [[1, 2], [2, 3]], + [[3, 2, 1], [4, 3, 2]], + ]; + } + + public function dataProviderForWriteCombine(): array + { + return [ + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview('::2'), + [11, 33, 55, 77, 99], + [11, 2, 33, 4, 55, 6, 77, 8, 99, 10], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview('::2') + ->subview(new MaskSelector([true, false, true, false, true])), + [11, 55, 99], + [11, 2, 3, 4, 55, 6, 7, 8, 99, 10], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview('::2') + ->subview(new MaskSelector([true, false, true, false, true])) + ->subview(new IndexListSelector([0, 2])), + [11, 99], + [11, 2, 3, 4, 5, 6, 7, 8, 99, 10], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview('::2') + ->subview(new MaskSelector([true, false, true, false, true])) + ->subview(new IndexListSelector([0, 2])) + ->subview('1:'), + [99], + [1, 2, 3, 4, 5, 6, 7, 8, 99, 10], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview(new MaskSelector([true, false, true, false, true, false, true, false, true, false])) + ->subview(new MaskSelector([true, false, true, false, true])) + ->subview(new MaskSelector([true, false, true])) + ->subview(new MaskSelector([false, true])), + [99], + [1, 2, 3, 4, 5, 6, 7, 8, 99, 10], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview(new MaskSelector(ArrayView::toUnlinkedView( + [true, false, true, false, true, false, true, false, true, false] + ))) + ->subview(new MaskSelector([true, false, true, false, true])) + ->subview(new MaskSelector([true, false, true])), + [11, 99], + [11, 2, 3, 4, 5, 6, 7, 8, 99, 10], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview(new IndexListSelector([0, 2, 4, 6, 8])) + ->subview(new IndexListSelector([0, 2, 4])) + ->subview(new IndexListSelector([0, 2])) + ->subview(new IndexListSelector([1])), + [99], + [1, 2, 3, 4, 5, 6, 7, 8, 99, 10], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview('::2') + ->subview('::2') + ->subview('::2') + ->subview('1:'), + [99], + [1, 2, 3, 4, 5, 6, 7, 8, 99, 10], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview(new SliceSelector(new Slice(null, null, 2))) + ->subview(new SliceSelector('::2')) + ->subview('::2'), + [11, 99], + [11, 2, 3, 4, 5, 6, 7, 8, 99, 10], + ], + [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + fn (array &$source) => ArrayView::toView($source) + ->subview(new SliceSelector(new Slice(null, null, 2))) + ->subview(new SliceSelector('::2')) + ->subview('::2'), + 111, + [111, 2, 3, 4, 5, 6, 7, 8, 111, 10], + ], + ]; + } +}