diff --git a/src/Injector.php b/src/Injector.php index 640fdcb..3d67da5 100644 --- a/src/Injector.php +++ b/src/Injector.php @@ -4,6 +4,7 @@ use ReflectionClass; use ReflectionFunction; use ReflectionNamedType; +use ReflectionParameter; class Injector { public function __construct( @@ -36,28 +37,7 @@ public function invoke( } foreach($refFunction->getParameters() as $refParam) { - /** @var ReflectionNamedType|null $refType */ - $refType = $refParam->getType(); - /** @var class-string $refParamTypeName */ - $refParamTypeName = $refType->getName(); - - try { - array_push( - $arguments, - $extraArgs[$refParamTypeName] ?? $this->container->get($refParamTypeName) - ); - } - catch(ServiceNotFoundException $exception) { - if($refType->allowsNull()) { - array_push( - $arguments, - null, - ); - } - else { - throw $exception; - } - } + array_push($arguments, $this->resolveParameter($refParam, $extraArgs)); } if($instance) { @@ -66,4 +46,77 @@ public function invoke( return $refFunction->invoke(...$arguments); } + + /** @param array $extraArgs */ + private function resolveParameter( + ReflectionParameter $refParam, + array $extraArgs, + ):mixed { + $refType = $refParam->getType(); + if(!$refType instanceof ReflectionNamedType) { + return $this->resolveUntypedParameter($refParam, $extraArgs); + } + + /** @var class-string $refParamTypeName */ + $refParamTypeName = $refType->getName(); + + if(array_key_exists($refParam->getName(), $extraArgs)) { + return $extraArgs[$refParam->getName()]; + } + if(array_key_exists($refParamTypeName, $extraArgs)) { + return $extraArgs[$refParamTypeName]; + } + if($refParam->isDefaultValueAvailable()) { + return $refParam->getDefaultValue(); + } + if($refType->isBuiltin()) { + return $this->resolveBuiltinParameter($refType, $refParamTypeName); + } + + return $this->resolveServiceParameter($refType, $refParamTypeName); + } + + /** @param array $extraArgs */ + private function resolveUntypedParameter( + ReflectionParameter $refParam, + array $extraArgs, + ):mixed { + if(array_key_exists($refParam->getName(), $extraArgs)) { + return $extraArgs[$refParam->getName()]; + } + if($refParam->isDefaultValueAvailable()) { + return $refParam->getDefaultValue(); + } + + return null; + } + + /** @param class-string $refParamTypeName */ + private function resolveBuiltinParameter( + ReflectionNamedType $refType, + string $refParamTypeName, + ):mixed { + if($refType->allowsNull()) { + return null; + } + + throw new ServiceNotFoundException($refParamTypeName); + } + + /** @param class-string $refParamTypeName */ + private function resolveServiceParameter( + ReflectionNamedType $refType, + string $refParamTypeName, + ):mixed { + try { + return $this->container->get($refParamTypeName); + } + catch(ServiceNotFoundException $exception) { + if($refType->allowsNull()) { + return null; + } + + throw $exception; + } + } } diff --git a/test/phpunit/InjectorTest.php b/test/phpunit/InjectorTest.php index ac62b0d..5da9afa 100644 --- a/test/phpunit/InjectorTest.php +++ b/test/phpunit/InjectorTest.php @@ -4,6 +4,7 @@ use DateTime; use GT\ServiceContainer\Container; use GT\ServiceContainer\Injector; +use GT\ServiceContainer\ServiceNotFoundException; use GT\ServiceContainer\Test\Example\Greeter; use PHPUnit\Framework\TestCase; @@ -56,4 +57,108 @@ public function testInvoke_noClass():void { self::assertCount(1, $invocationList); self::assertSame($greeter, $invocationList[0]); } + + public function testInvoke_usesDefaultScalarParameter():void { + $function = function(string $name = "World"):string { + return "Hello, $name!"; + }; + + $sut = new Injector(new Container()); + self::assertSame("Hello, World!", $sut->invoke(null, $function)); + } + + public function testInvoke_usesExtraArgByParameterName():void { + $function = function(string $name = "World"):string { + return "Hello, $name!"; + }; + + $sut = new Injector(new Container()); + self::assertSame( + "Hello, Cron!", + $sut->invoke(null, $function, ["name" => "Cron"]) + ); + } + + public function testInvoke_usesExtraArgByParameterType():void { + $dateTime = new DateTime("2026-06-22"); + $function = function(DateTime $dateTime):string { + return $dateTime->format("Y-m-d"); + }; + + $sut = new Injector(new Container()); + self::assertSame( + "2026-06-22", + $sut->invoke(null, $function, [DateTime::class => $dateTime]) + ); + } + + public function testInvoke_usesExtraArgForUntypedParameter():void { + $function = function($name = "World"):string { + return "Hello, $name!"; + }; + + $sut = new Injector(new Container()); + self::assertSame( + "Hello, Cron!", + $sut->invoke(null, $function, ["name" => "Cron"]) + ); + } + + public function testInvoke_usesDefaultUntypedParameter():void { + $function = function($name = "World"):string { + return "Hello, $name!"; + }; + + $sut = new Injector(new Container()); + self::assertSame("Hello, World!", $sut->invoke(null, $function)); + } + + public function testInvoke_setsMissingUntypedParameterToNull():void { + $function = function($name):mixed { + return $name; + }; + + $sut = new Injector(new Container()); + self::assertNull($sut->invoke(null, $function)); + } + + public function testInvoke_setsMissingNullableBuiltinParameterToNull():void { + $function = function(?string $name):mixed { + return $name; + }; + + $sut = new Injector(new Container()); + self::assertNull($sut->invoke(null, $function)); + } + + public function testInvoke_throwsWhenRequiredBuiltinParameterIsMissing():void { + $function = function(string $name):string { + return $name; + }; + + $sut = new Injector(new Container()); + + self::expectException(ServiceNotFoundException::class); + $sut->invoke(null, $function); + } + + public function testInvoke_setsMissingNullableServiceParameterToNull():void { + $function = function(?Greeter $greeter):mixed { + return $greeter; + }; + + $sut = new Injector(new Container()); + self::assertNull($sut->invoke(null, $function)); + } + + public function testInvoke_throwsWhenRequiredServiceParameterIsMissing():void { + $function = function(Greeter $greeter):Greeter { + return $greeter; + }; + + $sut = new Injector(new Container()); + + self::expectException(ServiceNotFoundException::class); + $sut->invoke(null, $function); + } }