From 7f800773f11b0c01d15e3df9fadcbd150b6dca4c Mon Sep 17 00:00:00 2001 From: Sergio Brighenti Date: Tue, 4 Jul 2023 11:52:01 +0200 Subject: [PATCH] fix variadic resolve --- src/Container.php | 23 +++++++++---- src/Exception/ContainerException.php | 2 +- tests/Feature/ContainerCallTest.php | 32 +++++++++++++++++++ tests/Feature/ContainerResolveTest.php | 29 +++++++++++++++++ tests/Fixtures/Resolve/MyEnum.php | 9 ++++++ .../Resolve/ResolvableClassWithConstant.php | 11 +++++++ .../Resolve/ResolvableClassWithEnum.php | 10 ++++++ 7 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 tests/Fixtures/Resolve/MyEnum.php create mode 100644 tests/Fixtures/Resolve/ResolvableClassWithConstant.php create mode 100644 tests/Fixtures/Resolve/ResolvableClassWithEnum.php diff --git a/src/Container.php b/src/Container.php index 70c7764..7868b36 100644 --- a/src/Container.php +++ b/src/Container.php @@ -32,7 +32,7 @@ public function __construct() /** * @template T * - * @param class-string $id + * @param class-string $id * @return T * * @inheritDoc @@ -132,7 +132,7 @@ public function call(callable|string|array $callable, array $arguments = []): mi } /** - * @param string $class + * @param string $class * @return object|string|null * * @throws ContainerException @@ -155,7 +155,7 @@ protected function resolve(string $class): object|string|null } /** - * @param ReflectionParameter[] $parameters + * @param ReflectionParameter[] $parameters * @return array|null[]|object[]|string[] * * @throws ContainerException @@ -167,20 +167,29 @@ protected function getArguments(array $parameters, $additional = []): array { $positionalArgs = array_filter($additional, 'is_numeric', ARRAY_FILTER_USE_KEY); - return array_map(function (ReflectionParameter $param) use (&$additional, &$positionalArgs) { + $resolved = []; + foreach ($parameters as $param) { $type = $param->getType()?->getName(); - return match (true) { + // variadic parameters can only be the last one + if ($param->isVariadic()) { + $resolved = array_merge($resolved, $additional[$param->getName()] ?? $positionalArgs); + break; + } + + $resolved[] = match (true) { $type !== null && $this->has($type) => $this->get($type), // via definitions array_key_exists( $param->getName(), $additional ) => $additional[$param->getName()], // defined by the user !empty($positionalArgs) => array_shift($positionalArgs), - $param->isOptional() => $param->getDefaultValue(), // use default when available + $param->isOptional() && $param->isDefaultValueAvailable() => $param->getDefaultValue(), // use default when available $type !== null && class_exists($type) && !enum_exists($type) => $this->resolve($type), // via reflection default => throw ContainerException::parameterNotResolvable($param), }; - }, $parameters); + } + + return $resolved; } } diff --git a/src/Exception/ContainerException.php b/src/Exception/ContainerException.php index 86d75d1..bc6f302 100644 --- a/src/Exception/ContainerException.php +++ b/src/Exception/ContainerException.php @@ -15,7 +15,7 @@ public static function invalidDefinition(string $id): self public static function parameterNotResolvable(ReflectionParameter $param): self { - return new self("Cannot resolve constructor parameter '\${$param->getName()}::{$param->getDeclaringClass()?->getName()}'"); + return new self("Cannot resolve parameter '\${$param->getName()}::{$param->getDeclaringClass()?->getName()}'"); } public static function invalidCallable(): self diff --git a/tests/Feature/ContainerCallTest.php b/tests/Feature/ContainerCallTest.php index 3566ebf..0272982 100644 --- a/tests/Feature/ContainerCallTest.php +++ b/tests/Feature/ContainerCallTest.php @@ -177,3 +177,35 @@ 'z' => 'zzzz', ]); })->expectException(ContainerException::class); + +it('can resolve a call a closure with arguments and variadic', function () { + $container = new Container(); + $container->bind(SimpleInterface::class, SimpleClass::class); + + $f = function (SimpleInterface $simple, string ...$z) { + return [$simple, $z]; + }; + + $result = $container->call($f, ['eee', 'aaa', 'zzz']); + + expect($result)->sequence( + fn ($e) => $e->toBeInstanceOf(SimpleClass::class), + fn ($e) => $e->toBe(['eee', 'aaa', 'zzz']), + ); +}); + +it('can resolve a call a closure with arguments and no variadic', function () { + $container = new Container(); + $container->bind(SimpleInterface::class, SimpleClass::class); + + $f = function (SimpleInterface $simple, string ...$z) { + return [$simple, $z]; + }; + + $result = $container->call($f); + + expect($result)->sequence( + fn ($e) => $e->toBeInstanceOf(SimpleClass::class), + fn ($e) => $e->toBe([]), + ); +}); diff --git a/tests/Feature/ContainerResolveTest.php b/tests/Feature/ContainerResolveTest.php index 0018254..dab6082 100644 --- a/tests/Feature/ContainerResolveTest.php +++ b/tests/Feature/ContainerResolveTest.php @@ -8,7 +8,9 @@ use SergiX44\Container\Tests\Fixtures\Resolve\ConcreteClass; use SergiX44\Container\Tests\Fixtures\Resolve\MoreNestedClass; use SergiX44\Container\Tests\Fixtures\Resolve\NestedClass; +use SergiX44\Container\Tests\Fixtures\Resolve\ResolvableClassWithConstant; use SergiX44\Container\Tests\Fixtures\Resolve\ResolvableClassWithDefault; +use SergiX44\Container\Tests\Fixtures\Resolve\ResolvableClassWithEnum; use SergiX44\Container\Tests\Fixtures\Resolve\SimpleClass; use SergiX44\Container\Tests\Fixtures\Resolve\SimpleClassWithConstructor; use SergiX44\Container\Tests\Fixtures\Resolve\SimpleInterface; @@ -51,6 +53,7 @@ ->and($instance->more->r)->toBeInstanceOf(ResolvableClassWithDefault::class); }); + it('can resolve a definition with constructor', function () { $container = new Container(); @@ -98,6 +101,32 @@ ->toBeInstanceOf(SimpleClass::class); }); +it('can resolve a definition with constructor default parameters as enum', function () { + $container = new Container(); + + $container->bind(SimpleInterface::class, SimpleClass::class); + + $instance = $container->get(ResolvableClassWithEnum::class); + + expect($instance) + ->toBeInstanceOf(ResolvableClassWithEnum::class) + ->and($instance->simple) + ->toBeInstanceOf(SimpleClass::class); +}); + +it('can resolve a definition with constructor default parameters as const', function () { + $container = new Container(); + + $container->bind(SimpleInterface::class, SimpleClass::class); + + $instance = $container->get(ResolvableClassWithConstant::class); + + expect($instance) + ->toBeInstanceOf(ResolvableClassWithConstant::class) + ->and($instance->simple) + ->toBeInstanceOf(SimpleClass::class); +}); + it('can resolve a definition with a callable', function () { $container = new Container(); diff --git a/tests/Fixtures/Resolve/MyEnum.php b/tests/Fixtures/Resolve/MyEnum.php new file mode 100644 index 0000000..b1061bb --- /dev/null +++ b/tests/Fixtures/Resolve/MyEnum.php @@ -0,0 +1,9 @@ +