Skip to content

Commit

Permalink
Merge pull request #9 from SergiX44/fix_for_variadic
Browse files Browse the repository at this point in the history
fix variadic resolve
  • Loading branch information
sergix44 committed Jul 4, 2023
2 parents 9925428 + 7f80077 commit 5451379
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 8 deletions.
23 changes: 16 additions & 7 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function __construct()
/**
* @template T
*
* @param class-string<T> $id
* @param class-string<T> $id
* @return T
*
* @inheritDoc
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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;
}
}
2 changes: 1 addition & 1 deletion src/Exception/ContainerException.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions tests/Feature/ContainerCallTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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([]),
);
});
29 changes: 29 additions & 0 deletions tests/Feature/ContainerResolveTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -51,6 +53,7 @@
->and($instance->more->r)->toBeInstanceOf(ResolvableClassWithDefault::class);
});


it('can resolve a definition with constructor', function () {
$container = new Container();

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

Expand Down
9 changes: 9 additions & 0 deletions tests/Fixtures/Resolve/MyEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace SergiX44\Container\Tests\Fixtures\Resolve;

enum MyEnum: string
{
case HEY = 'hey';
case HO = 'ho';
}
11 changes: 11 additions & 0 deletions tests/Fixtures/Resolve/ResolvableClassWithConstant.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace SergiX44\Container\Tests\Fixtures\Resolve;

class ResolvableClassWithConstant
{
private const MY_CONSTANT = 'my_constant';
public function __construct(public SimpleInterface $simple, public string $c = self::MY_CONSTANT)
{
}
}
10 changes: 10 additions & 0 deletions tests/Fixtures/Resolve/ResolvableClassWithEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace SergiX44\Container\Tests\Fixtures\Resolve;

class ResolvableClassWithEnum
{
public function __construct(public SimpleInterface $simple, public MyEnum $enum = MyEnum::HEY)
{
}
}

0 comments on commit 5451379

Please sign in to comment.