Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 75 additions & 22 deletions src/Injector.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use ReflectionClass;
use ReflectionFunction;
use ReflectionNamedType;
use ReflectionParameter;

class Injector {
public function __construct(
Expand Down Expand Up @@ -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) {
Expand All @@ -66,4 +46,77 @@ public function invoke(

return $refFunction->invoke(...$arguments);
}

/** @param array<string, mixed> $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<string, mixed> $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;
}
}
}
105 changes: 105 additions & 0 deletions test/phpunit/InjectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
}
Loading