From 08594e63bcb087de03a01a8f78d3057225cf7f6d Mon Sep 17 00:00:00 2001 From: Spomky Date: Thu, 30 Sep 2021 22:15:41 +0200 Subject: [PATCH] List and Map Objects tests and ArrayAccess --- src/IndefiniteLengthListObject.php | 70 +++++++++++++++++- src/IndefiniteLengthMapObject.php | 111 +++++++++++++++++++++++++--- src/ListObject.php | 65 ++++++++++++++++- src/MapObject.php | 105 ++++++++++++++++++++++++--- tests/ListObjectTest.php | 87 ++++++++++++++++++++++ tests/MapObjectTest.php | 112 +++++++++++++++++++++++++++++ 6 files changed, 526 insertions(+), 24 deletions(-) create mode 100644 tests/ListObjectTest.php create mode 100644 tests/MapObjectTest.php diff --git a/src/IndefiniteLengthListObject.php b/src/IndefiniteLengthListObject.php index ada20e1..d86958e 100644 --- a/src/IndefiniteLengthListObject.php +++ b/src/IndefiniteLengthListObject.php @@ -13,9 +13,12 @@ namespace CBOR; +use function array_key_exists; +use ArrayAccess; use ArrayIterator; use function count; use Countable; +use InvalidArgumentException; use Iterator; use IteratorAggregate; @@ -23,7 +26,7 @@ * @phpstan-implements IteratorAggregate * @final */ -class IndefiniteLengthListObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable +class IndefiniteLengthListObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable, ArrayAccess { private const MAJOR_TYPE = self::MAJOR_TYPE_LIST; private const ADDITIONAL_INFORMATION = self::LENGTH_INDEFINITE; @@ -81,6 +84,42 @@ public function add(CBORObject $item): self return $this; } + public function has(int $index): bool + { + return array_key_exists($index, $this->data); + } + + public function remove(int $index): self + { + if (!$this->has($index)) { + return $this; + } + unset($this->data[$index]); + $this->data = array_values($this->data); + + return $this; + } + + public function get(int $index): CBORObject + { + if (!$this->has($index)) { + throw new InvalidArgumentException('Index not found.'); + } + + return $this->data[$index]; + } + + public function set(int $index, CBORObject $object): self + { + if (!$this->has($index)) { + throw new InvalidArgumentException('Index not found.'); + } + + $this->data[$index] = $object; + + return $this; + } + /** * @deprecated The method will be removed on v3.0. No replacement */ @@ -96,4 +135,33 @@ public function getIterator(): Iterator { return new ArrayIterator($this->data); } + + public function offsetExists($offset): bool + { + return $this->has($offset); + } + + /** + * @param int $offset + */ + public function offsetGet($offset): CBORObject + { + return $this->get($offset); + } + + public function offsetSet($offset, $value): void + { + if (null === $offset) { + $this->add($value); + + return; + } + + $this->set($offset, $value); + } + + public function offsetUnset($offset): void + { + $this->remove($offset); + } } diff --git a/src/IndefiniteLengthMapObject.php b/src/IndefiniteLengthMapObject.php index 25946d5..4a52b35 100644 --- a/src/IndefiniteLengthMapObject.php +++ b/src/IndefiniteLengthMapObject.php @@ -13,6 +13,8 @@ namespace CBOR; +use function array_key_exists; +use ArrayAccess; use ArrayIterator; use function count; use Countable; @@ -24,7 +26,7 @@ * @phpstan-implements IteratorAggregate * @final */ -class IndefiniteLengthMapObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable +class IndefiniteLengthMapObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable, ArrayAccess { private const MAJOR_TYPE = self::MAJOR_TYPE_MAP; private const ADDITIONAL_INFORMATION = self::LENGTH_INDEFINITE; @@ -56,9 +58,66 @@ public function __toString(): string return $result; } + /** + * @deprecated The method will be removed on v3.0. Please use "add" instead + */ public function append(CBORObject $key, CBORObject $value): self { - $this->data[] = MapItem::create($key, $value); + return $this->add($key, $value); + } + + public function add(CBORObject $key, CBORObject $value): self + { + if (!$key instanceof Normalizable) { + throw new InvalidArgumentException('Invalid key. Shall be normalizable'); + } + $this->data[$key->normalize()] = MapItem::create($key, $value); + + return $this; + } + + /** + * @param int|string $key + */ + public function has($key): bool + { + return array_key_exists($key, $this->data); + } + + /** + * @param int|string $index + */ + public function remove($index): self + { + if (!$this->has($index)) { + return $this; + } + unset($this->data[$index]); + $this->data = array_values($this->data); + + return $this; + } + + /** + * @param int|string $index + */ + public function get($index): MapItem + { + if (!$this->has($index)) { + throw new InvalidArgumentException('Index not found.'); + } + + return $this->data[$index]; + } + + public function set(MapItem $object): self + { + $key = $object->getKey(); + if (!$key instanceof Normalizable) { + throw new InvalidArgumentException('Invalid key. Shall be normalizable'); + } + + $this->data[$key->normalize()] = $object; return $this; } @@ -84,17 +143,16 @@ public function getIterator(): Iterator */ public function normalize(): array { - $result = []; - foreach ($this->data as $object) { - $keyObject = $object->getKey(); - if (!$keyObject instanceof Normalizable) { + return array_reduce($this->data, static function (array $carry, MapItem $item): array { + $key = $item->getKey(); + if (!$key instanceof Normalizable) { throw new InvalidArgumentException('Invalid key. Shall be normalizable'); } - $valueObject = $object->getValue(); - $result[$keyObject->normalize()] = $valueObject instanceof Normalizable ? $valueObject->normalize() : $object; - } + $valueObject = $item->getValue(); + $carry[$key->normalize()] = $valueObject instanceof Normalizable ? $valueObject->normalize() : $valueObject; - return $result; + return $carry; + }, []); } /** @@ -106,4 +164,37 @@ public function getNormalizedData(bool $ignoreTags = false): array { return $this->normalize(); } + + /** + * @param int|string $offset + */ + public function offsetExists($offset): bool + { + return $this->has($offset); + } + + /** + * @param int|string $offset + */ + public function offsetGet($offset): MapItem + { + return $this->get($offset); + } + + public function offsetSet($offset, $value): void + { + if (!$offset instanceof CBORObject && !$offset instanceof Normalizable) { + throw new InvalidArgumentException('Invalid key'); + } + if (!$value instanceof CBORObject) { + throw new InvalidArgumentException('Invalid value'); + } + + $this->set(MapItem::create($offset, $value)); + } + + public function offsetUnset($offset): void + { + $this->remove($offset); + } } diff --git a/src/ListObject.php b/src/ListObject.php index 89b6df0..4bee838 100644 --- a/src/ListObject.php +++ b/src/ListObject.php @@ -14,6 +14,7 @@ namespace CBOR; use function array_key_exists; +use ArrayAccess; use ArrayIterator; use function count; use Countable; @@ -24,7 +25,7 @@ /** * @phpstan-implements IteratorAggregate */ -class ListObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable +class ListObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable, ArrayAccess { private const MAJOR_TYPE = self::MAJOR_TYPE_LIST; @@ -51,7 +52,7 @@ public function __construct(array $data = []) }, $data); parent::__construct(self::MAJOR_TYPE, $additionalInformation); - $this->data = $data; + $this->data = array_values($data); $this->length = $length; } @@ -84,15 +85,44 @@ public function add(CBORObject $object): self return $this; } + public function has(int $index): bool + { + return array_key_exists($index, $this->data); + } + + public function remove(int $index): self + { + if (!$this->has($index)) { + return $this; + } + unset($this->data[$index]); + $this->data = array_values($this->data); + [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data); + + return $this; + } + public function get(int $index): CBORObject { - if (!array_key_exists($index, $this->data)) { + if (!$this->has($index)) { throw new InvalidArgumentException('Index not found.'); } return $this->data[$index]; } + public function set(int $index, CBORObject $object): self + { + if (!$this->has($index)) { + throw new InvalidArgumentException('Index not found.'); + } + + $this->data[$index] = $object; + [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data); + + return $this; + } + /** * @return array */ @@ -125,4 +155,33 @@ public function getIterator(): Iterator { return new ArrayIterator($this->data); } + + public function offsetExists($offset): bool + { + return $this->has($offset); + } + + /** + * @param int $offset + */ + public function offsetGet($offset): CBORObject + { + return $this->get($offset); + } + + public function offsetSet($offset, $value): void + { + if (null === $offset) { + $this->add($value); + + return; + } + + $this->set($offset, $value); + } + + public function offsetUnset($offset): void + { + $this->remove($offset); + } } diff --git a/src/MapObject.php b/src/MapObject.php index 54b819d..3c01dd1 100644 --- a/src/MapObject.php +++ b/src/MapObject.php @@ -13,6 +13,8 @@ namespace CBOR; +use function array_key_exists; +use ArrayAccess; use ArrayIterator; use function count; use Countable; @@ -23,7 +25,7 @@ /** * @phpstan-implements IteratorAggregate */ -final class MapObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable +final class MapObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable, ArrayAccess { private const MAJOR_TYPE = self::MAJOR_TYPE_MAP; @@ -78,7 +80,58 @@ public function __toString(): string public function add(CBORObject $key, CBORObject $value): self { - $this->data[] = MapItem::create($key, $value); + if (!$key instanceof Normalizable) { + throw new InvalidArgumentException('Invalid key. Shall be normalizable'); + } + $this->data[$key->normalize()] = MapItem::create($key, $value); + [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data); + + return $this; + } + + /** + * @param int|string $key + */ + public function has($key): bool + { + return array_key_exists($key, $this->data); + } + + /** + * @param int|string $index + */ + public function remove($index): self + { + if (!$this->has($index)) { + return $this; + } + unset($this->data[$index]); + $this->data = array_values($this->data); + [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data); + + return $this; + } + + /** + * @param int|string $index + */ + public function get($index): MapItem + { + if (!$this->has($index)) { + throw new InvalidArgumentException('Index not found.'); + } + + return $this->data[$index]; + } + + public function set(MapItem $object): self + { + $key = $object->getKey(); + if (!$key instanceof Normalizable) { + throw new InvalidArgumentException('Invalid key. Shall be normalizable'); + } + + $this->data[$key->normalize()] = $object; [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data); return $this; @@ -102,17 +155,16 @@ public function getIterator(): Iterator */ public function normalize(): array { - $result = []; - foreach ($this->data as $object) { - $keyObject = $object->getKey(); - if (!$keyObject instanceof Normalizable) { + return array_reduce($this->data, static function (array $carry, MapItem $item): array { + $key = $item->getKey(); + if (!$key instanceof Normalizable) { throw new InvalidArgumentException('Invalid key. Shall be normalizable'); } - $valueObject = $object->getValue(); - $result[$keyObject->normalize()] = $valueObject instanceof Normalizable ? $valueObject->normalize() : $object; - } + $valueObject = $item->getValue(); + $carry[$key->normalize()] = $valueObject instanceof Normalizable ? $valueObject->normalize() : $valueObject; - return $result; + return $carry; + }, []); } /** @@ -124,4 +176,37 @@ public function getNormalizedData(bool $ignoreTags = false): array { return $this->normalize(); } + + /** + * @param int|string $offset + */ + public function offsetExists($offset): bool + { + return $this->has($offset); + } + + /** + * @param int|string $offset + */ + public function offsetGet($offset): MapItem + { + return $this->get($offset); + } + + public function offsetSet($offset, $value): void + { + if (!$offset instanceof CBORObject && !$offset instanceof Normalizable) { + throw new InvalidArgumentException('Invalid key'); + } + if (!$value instanceof CBORObject) { + throw new InvalidArgumentException('Invalid value'); + } + + $this->set(MapItem::create($offset, $value)); + } + + public function offsetUnset($offset): void + { + $this->remove($offset); + } } diff --git a/tests/ListObjectTest.php b/tests/ListObjectTest.php new file mode 100644 index 0000000..4512503 --- /dev/null +++ b/tests/ListObjectTest.php @@ -0,0 +1,87 @@ +add(TextStringObject::create('Hello')) + ->add(TextStringObject::create('World')) + ->add(UnsignedIntegerObject::create(1)) + ->add(UnsignedIntegerObject::create(2)) + ->set(2, UnsignedIntegerObject::create(3)) + ->remove(3) + ; + + $object2 = ListObject::create(); + $object2[] = TextStringObject::create('Hello'); + $object2[] = TextStringObject::create('World'); + $object2[] = UnsignedIntegerObject::create(1); + $object2[] = UnsignedIntegerObject::create(2); + $object2[2] = UnsignedIntegerObject::create(3); + unset($object2[3]); + + static::assertCount(3, $object1); + static::assertCount(3, $object2); + static::assertEquals(['Hello', 'World', 3], $object2->normalize()); + static::assertEquals($object1->normalize(), $object2->normalize()); + static::assertEquals((string) $object1, (string) $object2); + static::assertTrue(isset($object2[0])); + static::assertEquals(TextStringObject::create('World'), $object2[1]); + } + + /** + * @test + */ + public function anIndefiniteLengthListActsAsAnArray(): void + { + $object1 = IndefiniteLengthListObject::create() + ->add(TextStringObject::create('Hello')) + ->add(TextStringObject::create('World')) + ->add(UnsignedIntegerObject::create(1)) + ->add(UnsignedIntegerObject::create(2)) + ->set(2, UnsignedIntegerObject::create(3)) + ->remove(3) + ; + + $object2 = IndefiniteLengthListObject::create(); + $object2[] = TextStringObject::create('Hello'); + $object2[] = TextStringObject::create('World'); + $object2[] = UnsignedIntegerObject::create(1); + $object2[] = UnsignedIntegerObject::create(2); + $object2[2] = UnsignedIntegerObject::create(3); + unset($object2[3]); + + static::assertCount(3, $object1); + static::assertCount(3, $object2); + static::assertEquals(['Hello', 'World', 3], $object2->normalize()); + static::assertEquals($object1->normalize(), $object2->normalize()); + static::assertEquals((string) $object1, (string) $object2); + static::assertTrue(isset($object2[0])); + static::assertEquals(TextStringObject::create('World'), $object2[1]); + } +} diff --git a/tests/MapObjectTest.php b/tests/MapObjectTest.php new file mode 100644 index 0000000..994b416 --- /dev/null +++ b/tests/MapObjectTest.php @@ -0,0 +1,112 @@ +add(UnsignedIntegerObject::create(10), TextStringObject::create('Hello')) + ->add(NegativeIntegerObject::create(-150), TextStringObject::create('World')) + ->add(ByteStringObject::create('AZERTY'), UnsignedIntegerObject::create(1)) + ->add(TextStringObject::create('Test'), UnsignedIntegerObject::create(2)) + ->set(MapItem::create(TextStringObject::create('Test'), UnsignedIntegerObject::create(3))) + ->remove(3) + ; + + $object2 = MapObject::create(); + $object2[UnsignedIntegerObject::create(10)] = TextStringObject::create('Hello'); + $object2[NegativeIntegerObject::create(-150)] = TextStringObject::create('World'); + $object2[ByteStringObject::create('AZERTY')] = UnsignedIntegerObject::create(1); + $object2[TextStringObject::create('Test')] = UnsignedIntegerObject::create(2); + $object2[TextStringObject::create('Test')] = UnsignedIntegerObject::create(3); + unset($object2[3]); + + static::assertCount(4, $object1); + static::assertCount(4, $object2); + static::assertEquals([ + 10 => 'Hello', + -150 => 'World', + 'AZERTY' => 1, + 'Test' => 3, + ], $object2->normalize()); + static::assertEquals($object1->normalize(), $object2->normalize()); + static::assertEquals((string) $object1, (string) $object2); + static::assertTrue(isset($object2[10])); + static::assertTrue(isset($object2[-150])); + static::assertTrue(isset($object2['AZERTY'])); + static::assertTrue(isset($object2['Test'])); + static::assertEquals( + MapItem::create(NegativeIntegerObject::create(-150), TextStringObject::create('World')), + $object2[-150] + ); + } + + /** + * @test + */ + public function anIndefiniteLengthMapActsAsAnArray(): void + { + $object1 = IndefiniteLengthMapObject::create() + ->add(UnsignedIntegerObject::create(10), TextStringObject::create('Hello')) + ->add(NegativeIntegerObject::create(-150), TextStringObject::create('World')) + ->add(ByteStringObject::create('AZERTY'), UnsignedIntegerObject::create(1)) + ->add(TextStringObject::create('Test'), UnsignedIntegerObject::create(2)) + ->set(MapItem::create(TextStringObject::create('Test'), UnsignedIntegerObject::create(3))) + ->remove(3) + ; + + $object2 = IndefiniteLengthMapObject::create(); + $object2[UnsignedIntegerObject::create(10)] = TextStringObject::create('Hello'); + $object2[NegativeIntegerObject::create(-150)] = TextStringObject::create('World'); + $object2[ByteStringObject::create('AZERTY')] = UnsignedIntegerObject::create(1); + $object2[TextStringObject::create('Test')] = UnsignedIntegerObject::create(2); + $object2[TextStringObject::create('Test')] = UnsignedIntegerObject::create(3); + unset($object2[3]); + + static::assertCount(4, $object1); + static::assertCount(4, $object2); + static::assertEquals([ + 10 => 'Hello', + -150 => 'World', + 'AZERTY' => 1, + 'Test' => 3, + ], $object2->normalize()); + static::assertEquals($object1->normalize(), $object2->normalize()); + static::assertEquals((string) $object1, (string) $object2); + static::assertTrue(isset($object2[10])); + static::assertTrue(isset($object2[-150])); + static::assertTrue(isset($object2['AZERTY'])); + static::assertTrue(isset($object2['Test'])); + static::assertEquals( + MapItem::create(NegativeIntegerObject::create(-150), TextStringObject::create('World')), + $object2[-150] + ); + } +}