From 9fbca26768cc96ce260628eb782fb04e4bd6f7b4 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 27 Jan 2026 05:07:28 +0100 Subject: [PATCH 1/2] fix(reflection): correctly detect nullable types in TypeReflector --- packages/reflection/src/TypeReflector.php | 8 +++++++- .../tests/Fixtures/AnnulledInvoice.php | 9 +++++++++ .../reflection/tests/Fixtures/NullableClass.php | 9 +++++++++ packages/reflection/tests/TypeReflectorTest.php | 17 +++++++++++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 packages/reflection/tests/Fixtures/AnnulledInvoice.php create mode 100644 packages/reflection/tests/Fixtures/NullableClass.php diff --git a/packages/reflection/src/TypeReflector.php b/packages/reflection/src/TypeReflector.php index 64429ba19..05c70c930 100644 --- a/packages/reflection/src/TypeReflector.php +++ b/packages/reflection/src/TypeReflector.php @@ -266,7 +266,13 @@ private function resolveDefinition(PHPReflector|PHPReflectionType|string $reflec private function resolveIsNullable(PHPReflectionType|PHPReflector|string $reflector): bool { if (is_string($reflector)) { - return str_contains($this->definition, '?') || str_contains($this->definition, 'null'); + if (str_contains($this->definition, '?')) { + return true; + } + + $types = explode('|', $this->definition); + + return in_array('null', $types, strict: true); } if ($reflector instanceof PHPReflectionParameter || $reflector instanceof PHPReflectionProperty) { diff --git a/packages/reflection/tests/Fixtures/AnnulledInvoice.php b/packages/reflection/tests/Fixtures/AnnulledInvoice.php new file mode 100644 index 000000000..f5d5c7f8f --- /dev/null +++ b/packages/reflection/tests/Fixtures/AnnulledInvoice.php @@ -0,0 +1,9 @@ +isUnitEnum(), ); } + + public function test_is_nullable(): void + { + $this->assertTrue(new TypeReflector('?string')->isNullable()); + $this->assertTrue(new TypeReflector('string|null')->isNullable()); + $this->assertTrue(new TypeReflector('null')->isNullable()); + $this->assertFalse(new TypeReflector('string')->isNullable()); + } + + public function test_class_name_containing_null_is_not_nullable(): void + { + $this->assertFalse(new TypeReflector(AnnulledInvoice::class)->isNullable()); + $this->assertFalse(new TypeReflector(NullableClass::class)->isNullable()); + } } From 836e0032b2d7ba9c590ddb24d6ae8e47cd202f87 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 27 Jan 2026 20:59:47 +0100 Subject: [PATCH 2/2] fix(reflection): handle intersection types in nullable detection --- packages/reflection/src/TypeReflector.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/reflection/src/TypeReflector.php b/packages/reflection/src/TypeReflector.php index 05c70c930..acfcb0317 100644 --- a/packages/reflection/src/TypeReflector.php +++ b/packages/reflection/src/TypeReflector.php @@ -270,9 +270,10 @@ private function resolveIsNullable(PHPReflectionType|PHPReflector|string $reflec return true; } - $types = explode('|', $this->definition); - - return in_array('null', $types, strict: true); + return array_any( + array: preg_split('/[&|]/', $this->definition), + callback: static fn (string $type) => $type === 'null', + ); } if ($reflector instanceof PHPReflectionParameter || $reflector instanceof PHPReflectionProperty) {