From 6ec46a21826a597ac82cd945f8a5356a166a6f61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20B=C3=B6sing?= <2189546+boesing@users.noreply.github.com> Date: Wed, 21 Oct 2020 14:56:12 +0200 Subject: [PATCH] Use `OutOfBoundsException` instead of returning `null` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If `null` is being stored in a map or list, there is no way to know if the value is `null` or if the value does not exist. Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com> äFixed unit-tests --- src/ArrayInterface.php | 7 +++++-- src/Array_.php | 9 +++++---- src/Map.php | 9 ++++++++- src/MapInterface.php | 5 ++++- src/OrderedList.php | 14 ++++++++++++-- src/OrderedListInterface.php | 3 ++- tests/GenericMapTest.php | 8 ++++++++ tests/GenericOrderedListTest.php | 16 ++++++++++++---- 8 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/ArrayInterface.php b/src/ArrayInterface.php index 468eda0..bc8d62d 100644 --- a/src/ArrayInterface.php +++ b/src/ArrayInterface.php @@ -6,6 +6,7 @@ use Countable; use IteratorAggregate; +use OutOfBoundsException; /** * @internal @@ -23,14 +24,16 @@ interface ArrayInterface extends IteratorAggregate, Countable public function contains($element): bool; /** - * @psalm-return TValue|null + * @psalm-return TValue * @psalm-mutation-free + * @throws OutOfBoundsException if there are no values available. */ public function first(); /** - * @psalm-return TValue|null + * @psalm-return TValue * @psalm-mutation-free + * @throws OutOfBoundsException if there are no values available. */ public function last(); diff --git a/src/Array_.php b/src/Array_.php index a53df1e..1b9cf5f 100644 --- a/src/Array_.php +++ b/src/Array_.php @@ -6,6 +6,7 @@ use ArrayIterator; use DateTimeInterface; +use OutOfBoundsException; use Traversable; use Webmozart\Assert\Assert; @@ -59,8 +60,8 @@ public function contains($element): bool */ public function first() { - if ($this->data === []) { - return null; + if ($this->isEmpty()) { + throw new OutOfBoundsException('There are no values available.'); } return reset($this->data); @@ -71,8 +72,8 @@ public function first() */ public function last() { - if ($this->data === []) { - return null; + if ($this->isEmpty()) { + throw new OutOfBoundsException('There are no values available.'); } return end($this->data); diff --git a/src/Map.php b/src/Map.php index 07a6fa2..02035db 100644 --- a/src/Map.php +++ b/src/Map.php @@ -4,11 +4,13 @@ namespace Boesing\TypedArrays; +use OutOfBoundsException; use Webmozart\Assert\Assert; use function array_diff_ukey; use function array_filter; use function array_intersect_ukey; +use function array_key_exists; use function array_keys; use function array_map; use function array_merge; @@ -17,6 +19,7 @@ use function array_uintersect_uassoc; use function array_values; use function asort; +use function sprintf; use function uasort; use function usort; @@ -138,7 +141,11 @@ public function put($key, $value): MapInterface */ public function get($key) { - return $this->data[$key] ?? null; + if (! array_key_exists($key, $this->data)) { + throw new OutOfBoundsException(sprintf('There is no value stored for provided key: %s', (string) $key)); + } + + return $this->data[$key]; } public function intersect(MapInterface $other, ?callable $valueComparator = null): MapInterface diff --git a/src/MapInterface.php b/src/MapInterface.php index e680b4f..cdf402a 100644 --- a/src/MapInterface.php +++ b/src/MapInterface.php @@ -4,6 +4,8 @@ namespace Boesing\TypedArrays; +use OutOfBoundsException; + /** * @template TValue * @template-extends ArrayInterface @@ -91,7 +93,8 @@ public function put($key, $value): MapInterface; /** * @psalm-param string $key - * @psalm-return TValue|null + * @psalm-return TValue + * @throws OutOfBoundsException if key does not exist. * @psalm-mutation-free */ public function get($key); diff --git a/src/OrderedList.php b/src/OrderedList.php index 45fa12e..4c2a9ee 100644 --- a/src/OrderedList.php +++ b/src/OrderedList.php @@ -11,6 +11,7 @@ use function array_combine; use function array_fill; use function array_filter; +use function array_key_exists; use function array_keys; use function array_map; use function array_merge; @@ -23,6 +24,7 @@ use function is_callable; use function serialize; use function sort; +use function sprintf; use function usort; use const SORT_NATURAL; @@ -76,7 +78,11 @@ public function add($element): OrderedListInterface */ public function at(int $position) { - return $this->data[$position] ?? null; + if (! array_key_exists($position, $this->data)) { + throw new OutOfBoundsException(sprintf('There is no value stored in that position: %d', $position)); + } + + return $this->data[$position]; } public function sort(?callable $callback = null): OrderedListInterface @@ -187,7 +193,11 @@ public function unify( foreach ($instance->data as $value) { $identifier = $unificationIdentifierGenerator($value); - $unique = $unified->get($identifier) ?? $value; + try { + $unique = $unified->get($identifier); + } catch (OutOfBoundsException $exception) { + $unique = $value; + } if ($callback) { $unique = $callback($unique, $value); diff --git a/src/OrderedListInterface.php b/src/OrderedListInterface.php index 005c59d..00f3c75 100644 --- a/src/OrderedListInterface.php +++ b/src/OrderedListInterface.php @@ -20,7 +20,8 @@ interface OrderedListInterface extends ArrayInterface public function add($element): OrderedListInterface; /** - * @psalm-return TValue|null + * @psalm-return TValue + * @throws OutOfBoundsException If position does not exist. * @psalm-mutation-free */ public function at(int $position); diff --git a/tests/GenericMapTest.php b/tests/GenericMapTest.php index dd2e69e..f47c56d 100644 --- a/tests/GenericMapTest.php +++ b/tests/GenericMapTest.php @@ -9,6 +9,7 @@ use DateTimeImmutable; use Generator; use Lcobucci\Clock\FrozenClock; +use OutOfBoundsException; use PHPUnit\Framework\TestCase; use stdClass; @@ -459,4 +460,11 @@ public function testCanIntersectWithUserFunctions(): void ->toNativeArray() ); } + + public function testGetThrowsOutOfBoundsExceptionWhenKeyDoesNotExist(): void + { + $map = new GenericMap([]); + $this->expectException(OutOfBoundsException::class); + $map->get('foo'); + } } diff --git a/tests/GenericOrderedListTest.php b/tests/GenericOrderedListTest.php index bfdade2..27f5f03 100644 --- a/tests/GenericOrderedListTest.php +++ b/tests/GenericOrderedListTest.php @@ -202,7 +202,8 @@ public function testReturnsNullWhenSpecificItemNotFound(): void { $list = new GenericOrderedList([]); - self::assertNull($list->at(0)); + $this->expectException(OutOfBoundsException::class); + $list->at(0); } /** @@ -631,11 +632,18 @@ public function testFirstAndLastElementReturnsIdenticalObject(): void self::assertEquals($object2, $list->last()); } - public function testFirstAndLastReturnNullOnEmptyList(): void + public function testFirstThrowsOutOfBoundsExceptionWhenListIsEmpty(): void { $list = new GenericOrderedList([]); - self::assertNull($list->first()); - self::assertNull($list->last()); + $this->expectException(OutOfBoundsException::class); + $list->first(); + } + + public function testLastThrowsOutOfBoundsExceptionWhenListIsEmpty(): void + { + $list = new GenericOrderedList([]); + $this->expectException(OutOfBoundsException::class); + $list->last(); } public function testContainsDoesExactMatch(): void