diff --git a/.changelog/callable-composite-types.md b/.changelog/callable-composite-types.md new file mode 100644 index 0000000..85c7301 --- /dev/null +++ b/.changelog/callable-composite-types.md @@ -0,0 +1,6 @@ +--- +category: fixed +type: patch +--- + +Rejects union and intersection callable parameters with the documented invalid-type exception instead of an undefined reflection method error. diff --git a/src/Factory/CallableFactory.php b/src/Factory/CallableFactory.php index f2b8286..72db7de 100644 --- a/src/Factory/CallableFactory.php +++ b/src/Factory/CallableFactory.php @@ -20,7 +20,7 @@ use Closure; use ReflectionFunction; -use ReflectionType; +use ReflectionNamedType; use FastForward\Container\Exception\RuntimeException; use Psr\Container\ContainerInterface; @@ -77,7 +77,7 @@ private function getArguments(ContainerInterface $container, ReflectionFunction $arguments = []; foreach ($function->getParameters() as $parameter) { - if (! $parameter->getType() instanceof ReflectionType || $parameter->getType()->isBuiltin()) { + if (! $parameter->getType() instanceof ReflectionNamedType || $parameter->getType()->isBuiltin()) { throw RuntimeException::forInvalidParameterType($parameter->getName()); } diff --git a/tests/Factory/CallableFactoryTest.php b/tests/Factory/CallableFactoryTest.php index 0890b9b..fe17868 100644 --- a/tests/Factory/CallableFactoryTest.php +++ b/tests/Factory/CallableFactoryTest.php @@ -99,4 +99,52 @@ public function invokeWillThrowRuntimeExceptionIfParameterIsNotAClass(): void $factory($container); } + #[Test] + public function unionParameterReceivesInvalidTypeDiagnostic(): void + { + $factory = new CallableFactory(static fn(ContainerInterface|FactoryInterface $dependency) => $dependency); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Parameter "dependency" is not a valid type.'); + $factory($this->prophesize(ContainerInterface::class)->reveal()); + } + + #[Test] + public function intersectionParameterReceivesInvalidTypeDiagnostic(): void + { + $factory = new CallableFactory(static fn(ContainerInterface&FactoryInterface $dependency) => $dependency); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Parameter "dependency" is not a valid type.'); + $factory($this->prophesize(ContainerInterface::class)->reveal()); + } + + #[Test] + public function nullableNamedParameterStillResolvesDependency(): void + { + $container = $this->prophesize(ContainerInterface::class); + $dependency = $this->prophesize(FactoryInterface::class)->reveal(); + $container->get(FactoryInterface::class)->willReturn($dependency); + $factory = new CallableFactory(static fn(?FactoryInterface $dependency) => $dependency); + self::assertSame($dependency, $factory($container->reveal())); + } + + #[Test] + public function dnfParameterReceivesInvalidTypeDiagnostic(): void + { + $factory = new CallableFactory( + static fn((ContainerInterface&FactoryInterface)|ServiceProviderInterface $dependency) => $dependency + ); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Parameter "dependency" is not a valid type.'); + $factory($this->prophesize(ContainerInterface::class)->reveal()); + } + + #[Test] + public function untypedParameterReceivesInvalidTypeDiagnostic(): void + { + $factory = new CallableFactory(static fn($dependency) => $dependency); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Parameter "dependency" is not a valid type.'); + $factory($this->prophesize(ContainerInterface::class)->reveal()); + } + }