From 9a4cef4d28472e9963f88109641e81e97fa8435a Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 17 Jul 2026 03:55:07 +0200 Subject: [PATCH] fix: preserve null array-access values --- packages/http/src/RequestHeaders.php | 10 +++++++--- packages/http/tests/GenericRequestTest.php | 1 + packages/support/src/Arr/IsIterable.php | 6 +++++- packages/support/tests/Arr/ImmutableArrayTest.php | 8 ++++++++ packages/support/tests/Arr/ToArrayTest.php | 12 ++++++++---- packages/support/tests/Arr/WrapTest.php | 6 ++++-- packages/view/src/Parser/TokenCollection.php | 6 ++++-- packages/view/tests/TempestViewParserTest.php | 9 +++++++++ 8 files changed, 46 insertions(+), 12 deletions(-) diff --git a/packages/http/src/RequestHeaders.php b/packages/http/src/RequestHeaders.php index 72af828a13..b4253b906c 100644 --- a/packages/http/src/RequestHeaders.php +++ b/packages/http/src/RequestHeaders.php @@ -33,17 +33,21 @@ public function offsetExists(mixed $offset): bool { $offset = strtolower($offset); - return isset($this->headers[$offset]); + return array_key_exists($offset, $this->headers); } - public function offsetGet(mixed $offset): string + public function offsetGet(mixed $offset): ?string { return $this->get((string) $offset); } public function get(string $name, ?string $default = null): ?string { - return $this->headers[strtolower($name)] ?? $default; + $name = strtolower($name); + + return array_key_exists($name, $this->headers) + ? $this->headers[$name] + : $default; } public function has(string $name): bool diff --git a/packages/http/tests/GenericRequestTest.php b/packages/http/tests/GenericRequestTest.php index f4a2e9f61a..103266886d 100644 --- a/packages/http/tests/GenericRequestTest.php +++ b/packages/http/tests/GenericRequestTest.php @@ -30,6 +30,7 @@ public function test_normalizes_header_access(): void $this->assertSame($upperCaseValue, $request->headers['uppercase']); $this->assertSame($upperCaseValue, $request->headers['UPPerCasE']); $this->assertSame($lowerCaseValue, $request->headers['lowercase']); + $this->assertNull($request->headers['missing']); $this->assertSame($upperCaseValue, $request->headers->get('UpperCase')); $this->assertEquals( diff --git a/packages/support/src/Arr/IsIterable.php b/packages/support/src/Arr/IsIterable.php index e293451e42..2d778d5b49 100644 --- a/packages/support/src/Arr/IsIterable.php +++ b/packages/support/src/Arr/IsIterable.php @@ -39,7 +39,11 @@ public function offsetExists(mixed $offset): bool public function offsetGet(mixed $offset): mixed { - return $this->value[$offset] ?? throw new OffsetDidNotExist(); + if (! array_key_exists($offset, $this->value)) { + throw new OffsetDidNotExist(); + } + + return $this->value[$offset]; } public function offsetSet(mixed $offset, mixed $value): void diff --git a/packages/support/tests/Arr/ImmutableArrayTest.php b/packages/support/tests/Arr/ImmutableArrayTest.php index 77ffa5a231..a65af892f0 100644 --- a/packages/support/tests/Arr/ImmutableArrayTest.php +++ b/packages/support/tests/Arr/ImmutableArrayTest.php @@ -67,6 +67,14 @@ public function test_add_diverse_values(): void ); } + public function test_null_values_are_accessible_by_offset(): void + { + $collection = new ImmutableArray(['key' => null]); + + $this->assertTrue(isset($collection['key'])); + $this->assertNull($collection['key']); + } + public function test_remove_with_basic_keys(): void { $collection = new ImmutableArray([1, 2, 3]); diff --git a/packages/support/tests/Arr/ToArrayTest.php b/packages/support/tests/Arr/ToArrayTest.php index 1e6445cb13..8c25b0a398 100644 --- a/packages/support/tests/Arr/ToArrayTest.php +++ b/packages/support/tests/Arr/ToArrayTest.php @@ -57,12 +57,14 @@ public function test_array_access_and_countable_objects_are_converted_to_arrays( public function offsetExists($offset): bool { - return isset($this->data[$offset]); + return array_key_exists($offset, $this->data); } public function offsetGet($offset): mixed { - return $this->data[$offset] ?? null; + return array_key_exists($offset, $this->data) + ? $this->data[$offset] + : null; } public function offsetSet($offset, $value): void @@ -97,12 +99,14 @@ public function test_array_access_without_traversable_or_countable(): void public function offsetExists($offset): bool { - return isset($this->data[$offset]); + return array_key_exists($offset, $this->data); } public function offsetGet($offset): mixed { - return $this->data[$offset] ?? null; + return array_key_exists($offset, $this->data) + ? $this->data[$offset] + : null; } public function offsetSet($offset, $value): void diff --git a/packages/support/tests/Arr/WrapTest.php b/packages/support/tests/Arr/WrapTest.php index 016cd79d19..df52502b8f 100644 --- a/packages/support/tests/Arr/WrapTest.php +++ b/packages/support/tests/Arr/WrapTest.php @@ -58,12 +58,14 @@ public function test_array_access_and_countable_objects_are_not_converted_to_arr public function offsetExists($offset): bool { - return isset($this->data[$offset]); + return array_key_exists($offset, $this->data); } public function offsetGet($offset): mixed { - return $this->data[$offset] ?? null; + return array_key_exists($offset, $this->data) + ? $this->data[$offset] + : null; } public function offsetSet($offset, $value): void diff --git a/packages/view/src/Parser/TokenCollection.php b/packages/view/src/Parser/TokenCollection.php index b2f63a95a4..c9bc0ae5cf 100644 --- a/packages/view/src/Parser/TokenCollection.php +++ b/packages/view/src/Parser/TokenCollection.php @@ -40,12 +40,14 @@ public function __debugInfo(): array public function offsetExists(mixed $offset): bool { - return isset($this->tokens[$offset]); + return array_key_exists($offset, $this->tokens); } public function offsetGet(mixed $offset): mixed { - return $this->tokens[$offset] ?? null; + return array_key_exists($offset, $this->tokens) + ? $this->tokens[$offset] + : null; } public function offsetSet(mixed $offset, mixed $value): void diff --git a/packages/view/tests/TempestViewParserTest.php b/packages/view/tests/TempestViewParserTest.php index c38135f419..5848b52c9e 100644 --- a/packages/view/tests/TempestViewParserTest.php +++ b/packages/view/tests/TempestViewParserTest.php @@ -38,6 +38,15 @@ public function test_parser(): void HTML, $parsed->compile()); } + public function test_token_collection_treats_null_as_an_existing_offset(): void + { + $tokens = new TokenCollection(); + $tokens['nullable'] = null; + + $this->assertTrue(isset($tokens['nullable'])); + $this->assertNull($tokens['nullable']); + } + public function test_parse_self_closing_tag_with_attributes(): void { $tokens = new TokenCollection([