From b9f9df199997c16eb49e5f71219fa8acec4111aa Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 21 Dec 2023 23:03:29 +0400 Subject: [PATCH 1/6] Support \Stringable context; InjectorInterface may receive a non-string context; pass \ReflectionParameter in extended Injectors --- src/Core/src/Container.php | 2 +- src/Core/src/Internal/Container.php | 2 +- src/Core/src/Internal/Factory.php | 70 +++++++++++++++---- src/Core/src/Internal/Factory/Ctx.php | 2 +- src/Core/src/Internal/Resolver.php | 38 ++-------- .../Fixtures/ExtendedContextInjector.php | 19 +++++ src/Core/tests/InjectableTest.php | 13 ++++ 7 files changed, 97 insertions(+), 49 deletions(-) create mode 100644 src/Core/tests/Fixtures/ExtendedContextInjector.php diff --git a/src/Core/src/Container.php b/src/Core/src/Container.php index 8af69d83a..e3e385462 100644 --- a/src/Core/src/Container.php +++ b/src/Core/src/Container.php @@ -114,7 +114,7 @@ public function validateArguments(ContextFunction $reflection, array $arguments * @throws ContainerException * @throws \Throwable */ - public function make(string $alias, array $parameters = [], string $context = null): mixed + public function make(string $alias, array $parameters = [], \Stringable|string|null $context = null): mixed { /** @psalm-suppress TooManyArguments */ return $this->factory->make($alias, $parameters, $context); diff --git a/src/Core/src/Internal/Container.php b/src/Core/src/Internal/Container.php index 6ec2620a2..ad3b69fba 100644 --- a/src/Core/src/Internal/Container.php +++ b/src/Core/src/Internal/Container.php @@ -47,7 +47,7 @@ public function __construct(Registry $constructor) * @throws ContainerException * @throws \Throwable */ - public function get(string|Autowire $id, string $context = null): mixed + public function get(string|Autowire $id, \Stringable|string|null $context = null): mixed { if ($id instanceof Autowire) { return $id->resolve($this->factory); diff --git a/src/Core/src/Internal/Factory.php b/src/Core/src/Internal/Factory.php index de56bbe53..76c1735b5 100644 --- a/src/Core/src/Internal/Factory.php +++ b/src/Core/src/Internal/Factory.php @@ -6,13 +6,14 @@ use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; +use ReflectionFunctionAbstract as ContextFunction; +use ReflectionParameter; use Spiral\Core\Attribute\Finalize; use Spiral\Core\Attribute\Scope as ScopeAttribute; use Spiral\Core\Attribute\Singleton; use Spiral\Core\BinderInterface; use Spiral\Core\Config\Injectable; use Spiral\Core\Config\DeferredFactory; -use Spiral\Core\Container\Autowire; use Spiral\Core\Container\InjectorInterface; use Spiral\Core\Container\SingletonInterface; use Spiral\Core\Exception\Container\AutowireException; @@ -29,6 +30,7 @@ use Spiral\Core\Internal\Factory\Ctx; use Spiral\Core\InvokerInterface; use Spiral\Core\ResolverInterface; +use Stringable; use WeakReference; /** @@ -64,7 +66,7 @@ public function __construct(Registry $constructor) * * @throws \Throwable */ - public function make(string $alias, array $parameters = [], string $context = null): mixed + public function make(string $alias, array $parameters = [], string|Stringable $context = null): mixed { if ($parameters === [] && \array_key_exists($alias, $this->state->singletons)) { return $this->state->singletons[$alias]; @@ -110,7 +112,7 @@ public function make(string $alias, array $parameters = [], string $context = nu private function resolveInjector( Injectable $binding, string $alias, - ?string $context, + Stringable|string|null $context, array $arguments, ) { $ctx = new Ctx(alias: $alias, class: $alias, parameter: $context); @@ -142,10 +144,34 @@ private function resolveInjector( ); } - /** - * @psalm-suppress RedundantCondition - */ - $instance = $injectorInstance->createInjection($reflection, $ctx->parameter); + // todo test with anonymous classes + /** @var array, \ReflectionMethod|false> $cache True means extended injector */ + static $cache = []; + $extended = $cache[$injectorInstance::class] ??= (static function (\ReflectionType $type) { + if ($type::class === \ReflectionUnionType::class) { + foreach ($type->getTypes() as $t) { + if (!$t->isBuiltin()) { + return true; + } + // if (\in_array($t->getName(), [ReflectionParameter::class, Stringable::class, 'object'], true)) { + // return true; + // } + } + } + + return (string)$type === 'mixed'; + })( + ($refMethod = new \ReflectionMethod($injectorInstance, 'createInjection')) + ->getParameters()[1]->getType() + ) ? $refMethod : false; + + $asIs = $extended && (\is_string($context) || $this->validateArguments($extended, [$reflection, $context])); + $instance = $injectorInstance->createInjection($reflection, match(true) { + $asIs => $context, + $context instanceof ReflectionParameter => $context->getName(), + default => (string)$context, + }); + if (!$reflection->isInstance($instance)) { throw new InjectionException( \sprintf( @@ -164,7 +190,7 @@ private function resolveInjector( private function resolveAlias( \Spiral\Core\Config\Alias $binding, string $alias, - ?string $context, + Stringable|string|null $context, array $arguments, ): mixed { $result = $binding->alias === $alias @@ -185,7 +211,7 @@ private function resolveAlias( private function resolveShared( \Spiral\Core\Config\Shared $binding, string $alias, - ?string $context, + Stringable|string|null $context, array $arguments, ): object { $avoidCache = $arguments !== []; @@ -200,7 +226,7 @@ private function resolveShared( private function resolveAutowire( \Spiral\Core\Config\Autowire $binding, string $alias, - ?string $context, + Stringable|string|null $context, array $arguments, ): mixed { $instance = $binding->autowire->resolve($this, $arguments); @@ -212,7 +238,7 @@ private function resolveAutowire( private function resolveFactory( \Spiral\Core\Config\Factory|DeferredFactory $binding, string $alias, - ?string $context, + Stringable|string|null $context, array $arguments, ): mixed { $ctx = new Ctx(alias: $alias, class: $alias, parameter: $context, singleton: $binding->singleton); @@ -234,7 +260,7 @@ private function resolveFactory( private function resolveWeakReference( \Spiral\Core\Config\WeakReference $binding, string $alias, - ?string $context, + Stringable|string|null $context, array $arguments, ): ?object { $avoidCache = $arguments !== []; @@ -269,8 +295,11 @@ private function resolveWeakReference( return $binding->reference->get(); } - private function resolveWithoutBinding(string $alias, array $parameters = [], string $context = null): mixed - { + private function resolveWithoutBinding( + string $alias, + array $parameters = [], + Stringable|string|null $context = null + ): mixed { $parent = $this->scope->getParent(); if ($parent !== null) { @@ -302,7 +331,7 @@ private function resolveWithoutBinding(string $alias, array $parameters = [], st try { //No direct instructions how to construct class, make is automatically return $this->autowire( - new Ctx(alias: $alias, class: $alias, parameter: $context), + new Ctx(alias: $alias, class: $alias, parameter: (string)$context), $parameters, ); } finally { @@ -527,4 +556,15 @@ private function runInflector(object $instance): object return $instance; } + + private function validateArguments(ContextFunction $reflection, array $arguments = []): bool + { + try { + $this->resolver->validateArguments($reflection, $arguments); + } catch (\Throwable) { + return false; + } + + return true; + } } diff --git a/src/Core/src/Internal/Factory/Ctx.php b/src/Core/src/Internal/Factory/Ctx.php index f6caef200..a34035290 100644 --- a/src/Core/src/Internal/Factory/Ctx.php +++ b/src/Core/src/Internal/Factory/Ctx.php @@ -17,7 +17,7 @@ final class Ctx public function __construct( public readonly string $alias, public string $class, - public ?string $parameter = null, + public \Stringable|string|null $parameter = null, public ?bool $singleton = null, public ?\ReflectionClass $reflection = null, ) { diff --git a/src/Core/src/Internal/Resolver.php b/src/Core/src/Internal/Resolver.php index a5d6924c7..55634f476 100644 --- a/src/Core/src/Internal/Resolver.php +++ b/src/Core/src/Internal/Resolver.php @@ -208,7 +208,7 @@ private function resolveParameter(ReflectionParameter $parameter, ResolvingState $types = $reflectionType instanceof ReflectionNamedType ? [$reflectionType] : $reflectionType->getTypes(); foreach ($types as $namedType) { try { - if ($this->resolveNamedType($state, $parameter, $namedType, $validate)) { + if (!$namedType->isBuiltin() && $this->resolveObject($state, $namedType, $parameter, $validate,)) { return true; } } catch (Throwable $e) { @@ -237,45 +237,21 @@ private function resolveParameter(ReflectionParameter $parameter, ResolvingState throw $error; } - /** - * Resolve single named type. Returns {@see true} if argument was resolved. - * - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - * - * @return bool - */ - private function resolveNamedType( - ResolvingState $state, - ReflectionParameter $parameter, - ReflectionNamedType $typeRef, - bool $validate, - ) { - return !$typeRef->isBuiltin() && $this->resolveObjectParameter( - $state, - $typeRef->getName(), - $parameter->getName(), - $validate ? $parameter : null, - ); - } - /** * Resolve argument by class name and context. Returns {@see true} if argument resolved. * - * @psalm-param class-string $class - * * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ - private function resolveObjectParameter( + private function resolveObject( ResolvingState $state, - string $class, - string $context, - ReflectionParameter $validateWith = null, + ReflectionNamedType $type, + ReflectionParameter $parameter, + bool $validateWith = false, ): bool { /** @psalm-suppress TooManyArguments */ - $argument = $this->container->get($class, $context); - $this->processArgument($state, $argument, $validateWith); + $argument = $this->container->get($type->getName(), $parameter); + $this->processArgument($state, $argument, $validateWith ? $parameter : null); return true; } diff --git a/src/Core/tests/Fixtures/ExtendedContextInjector.php b/src/Core/tests/Fixtures/ExtendedContextInjector.php new file mode 100644 index 000000000..501cd3c76 --- /dev/null +++ b/src/Core/tests/Fixtures/ExtendedContextInjector.php @@ -0,0 +1,19 @@ + + */ +class ExtendedContextInjector implements InjectorInterface +{ + public function createInjection(\ReflectionClass $class, \ReflectionParameter|string|null $context = null): object + { + return (object)['context' => $context]; + } +} diff --git a/src/Core/tests/InjectableTest.php b/src/Core/tests/InjectableTest.php index 7f00cd3d9..eb9485f7b 100644 --- a/src/Core/tests/InjectableTest.php +++ b/src/Core/tests/InjectableTest.php @@ -13,6 +13,7 @@ use Spiral\Core\Container; use Spiral\Core\Exception\Container\AutowireException; use Spiral\Core\Exception\Container\InjectionException; +use Spiral\Tests\Core\Fixtures\ExtendedContextInjector; use Spiral\Tests\Core\Fixtures\InjectableClassChildImplementation; use Spiral\Tests\Core\Fixtures\InjectableClassChildInterface; use Spiral\Tests\Core\Fixtures\InjectableClassInterface; @@ -20,6 +21,7 @@ use Spiral\Tests\Core\Fixtures\InvalidInjector; use Spiral\Tests\Core\Fixtures\SampleClass; use Spiral\Tests\Core\Fixtures\TestConfig; +use stdClass; class InjectableTest extends TestCase { @@ -190,6 +192,17 @@ public function testInjectableInheritance(string $class): void $container->get($class); } + public function testExtendedInjector(): void + { + $container = new Container(); + $container->bindInjector(stdClass::class, ExtendedContextInjector::class); + + $result = $container->invoke(fn(stdClass $dt) => $dt); + + $this->assertInstanceOf(stdClass::class, $result); + $this->assertInstanceOf(\ReflectionParameter::class, $result->context); + } + /** * @param TestConfig $contextArgument */ From 5261ae464f5053b832575a4eb7001e2c9d074c7f Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Fri, 22 Dec 2023 22:11:02 +0400 Subject: [PATCH 2/6] Add tests; optimizations and cleanup --- src/Core/src/Attribute/Singleton.php | 2 - src/Core/src/Container/InjectorInterface.php | 2 +- src/Core/src/Internal/Factory.php | 70 +++++++------------ src/Core/src/Internal/Factory/Ctx.php | 2 +- src/Core/src/Internal/Resolver.php | 2 +- .../Fixtures/ExtendedContextInjector.php | 4 +- src/Core/tests/InjectableTest.php | 33 +++++++++ 7 files changed, 64 insertions(+), 51 deletions(-) diff --git a/src/Core/src/Attribute/Singleton.php b/src/Core/src/Attribute/Singleton.php index 014289d43..fd9c04cb9 100644 --- a/src/Core/src/Attribute/Singleton.php +++ b/src/Core/src/Attribute/Singleton.php @@ -4,8 +4,6 @@ namespace Spiral\Core\Attribute; -use Spiral\Core\Internal\Factory\Ctx; - /** * Mark class as singleton. */ diff --git a/src/Core/src/Container/InjectorInterface.php b/src/Core/src/Container/InjectorInterface.php index 9ac4e0add..bd8a591f5 100644 --- a/src/Core/src/Container/InjectorInterface.php +++ b/src/Core/src/Container/InjectorInterface.php @@ -24,10 +24,10 @@ interface InjectorInterface * * @param \ReflectionClass $class Request class type. * @param string|null $context Parameter or alias name. - * @psalm-assert \ReflectionClass $class * * @return TClass * + * @psalm-assert \ReflectionClass $class * @throws ContainerException */ public function createInjection(\ReflectionClass $class, ?string $context = null): object; diff --git a/src/Core/src/Internal/Factory.php b/src/Core/src/Internal/Factory.php index 76c1735b5..276ca4aff 100644 --- a/src/Core/src/Internal/Factory.php +++ b/src/Core/src/Internal/Factory.php @@ -62,11 +62,11 @@ public function __construct(Registry $constructor) } /** - * @param string|null $context Related to parameter caused injection if any. + * @param Stringable|string|null $context Related to parameter caused injection if any. * * @throws \Throwable */ - public function make(string $alias, array $parameters = [], string|Stringable $context = null): mixed + public function make(string $alias, array $parameters = [], Stringable|string|null $context = null): mixed { if ($parameters === [] && \array_key_exists($alias, $this->state->singletons)) { return $this->state->singletons[$alias]; @@ -96,7 +96,11 @@ public function make(string $alias, array $parameters = [], string|Stringable $c DeferredFactory::class, \Spiral\Core\Config\Factory::class => $this->resolveFactory($binding, $alias, $context, $parameters), \Spiral\Core\Config\Shared::class => $this->resolveShared($binding, $alias, $context, $parameters), - Injectable::class => $this->resolveInjector($binding, $alias, $context, $parameters), + Injectable::class => $this->resolveInjector( + $binding, + new Ctx(alias: $alias, class: $alias, context: $context), + $parameters, + ), \Spiral\Core\Config\Scalar::class => $binding->value, \Spiral\Core\Config\WeakReference::class => $this ->resolveWeakReference($binding, $alias, $context, $parameters), @@ -109,22 +113,11 @@ public function make(string $alias, array $parameters = [], string|Stringable $c } } - private function resolveInjector( - Injectable $binding, - string $alias, - Stringable|string|null $context, - array $arguments, - ) { - $ctx = new Ctx(alias: $alias, class: $alias, parameter: $context); - - // We have to construct class using external injector when we know exact context - if ($arguments !== []) { - // todo factory? - } - - $class = $ctx->class; + private function resolveInjector(Injectable $binding, Ctx $ctx, array $arguments) + { + $context = $ctx->context; try { - $ctx->reflection = $reflection = new \ReflectionClass($class); + $reflection = $ctx->reflection ??= new \ReflectionClass($ctx->class); } catch (\ReflectionException $e) { throw new ContainerException($e->getMessage(), $e->getCode(), $e); } @@ -144,29 +137,18 @@ private function resolveInjector( ); } - // todo test with anonymous classes - /** @var array, \ReflectionMethod|false> $cache True means extended injector */ + // todo wat should we do with arguments? + /** @var array, \ReflectionMethod|false> $cache reflection for extended injectors */ static $cache = []; - $extended = $cache[$injectorInstance::class] ??= (static function (\ReflectionType $type) { - if ($type::class === \ReflectionUnionType::class) { - foreach ($type->getTypes() as $t) { - if (!$t->isBuiltin()) { - return true; - } - // if (\in_array($t->getName(), [ReflectionParameter::class, Stringable::class, 'object'], true)) { - // return true; - // } - } - } - - return (string)$type === 'mixed'; - })( + $extended = $cache[$injectorInstance::class] ??= (static fn(\ReflectionType $type): bool => + $type::class === \ReflectionUnionType::class || (string)$type === 'mixed' + )( ($refMethod = new \ReflectionMethod($injectorInstance, 'createInjection')) ->getParameters()[1]->getType() ) ? $refMethod : false; $asIs = $extended && (\is_string($context) || $this->validateArguments($extended, [$reflection, $context])); - $instance = $injectorInstance->createInjection($reflection, match(true) { + $instance = $injectorInstance->createInjection($reflection, match (true) { $asIs => $context, $context instanceof ReflectionParameter => $context->getName(), default => (string)$context, @@ -183,7 +165,7 @@ private function resolveInjector( return $instance; } finally { - $this->state->bindings[$reflection->getName()] ??= $binding; //new Injector($injector); + $this->state->bindings[$reflection->getName()] ??= $binding; } } @@ -195,7 +177,7 @@ private function resolveAlias( ): mixed { $result = $binding->alias === $alias ? $this->autowire( - new Ctx(alias: $alias, class: $binding->alias, parameter: $context, singleton: $binding->singleton), + new Ctx(alias: $alias, class: $binding->alias, context: $context, singleton: $binding->singleton), $arguments, ) //Binding is pointing to something else @@ -217,7 +199,7 @@ private function resolveShared( $avoidCache = $arguments !== []; return $avoidCache ? $this->createInstance( - new Ctx(alias: $alias, class: $binding->value::class, parameter: $context), + new Ctx(alias: $alias, class: $binding->value::class, context: $context), $arguments, ) : $binding->value; @@ -231,7 +213,7 @@ private function resolveAutowire( ): mixed { $instance = $binding->autowire->resolve($this, $arguments); - $ctx = new Ctx(alias: $alias, class: $alias, parameter: $context, singleton: $binding->singleton); + $ctx = new Ctx(alias: $alias, class: $alias, context: $context, singleton: $binding->singleton); return $this->validateNewInstance($instance, $ctx, $arguments); } @@ -241,7 +223,7 @@ private function resolveFactory( Stringable|string|null $context, array $arguments, ): mixed { - $ctx = new Ctx(alias: $alias, class: $alias, parameter: $context, singleton: $binding->singleton); + $ctx = new Ctx(alias: $alias, class: $alias, context: $context, singleton: $binding->singleton); try { $instance = $binding::class === \Spiral\Core\Config\Factory::class && $binding->getParametersCount() === 0 ? ($binding->factory)() @@ -270,7 +252,7 @@ private function resolveWeakReference( $this->tracer->push(false, alias: $alias, source: WeakReference::class, context: $context); $object = $this->createInstance( - new Ctx(alias: $alias, class: $alias, parameter: $context), + new Ctx(alias: $alias, class: $alias, context: $context), $arguments, ); if ($avoidCache) { @@ -331,7 +313,7 @@ private function resolveWithoutBinding( try { //No direct instructions how to construct class, make is automatically return $this->autowire( - new Ctx(alias: $alias, class: $alias, parameter: (string)$context), + new Ctx(alias: $alias, class: $alias, context: $context), $parameters, ); } finally { @@ -424,8 +406,8 @@ private function createInstance( } //We have to construct class using external injector when we know exact context - if ($parameters === [] && $this->binder->hasInjector($class)) { - return $this->resolveInjector($this->state->bindings[$ctx->class], $ctx->class, $ctx->parameter, $parameters); + if ($this->binder->hasInjector($class)) { + return $this->resolveInjector($this->state->bindings[$ctx->class], $ctx, $parameters); } if (!$reflection->isInstantiable()) { diff --git a/src/Core/src/Internal/Factory/Ctx.php b/src/Core/src/Internal/Factory/Ctx.php index a34035290..6fbb88788 100644 --- a/src/Core/src/Internal/Factory/Ctx.php +++ b/src/Core/src/Internal/Factory/Ctx.php @@ -17,7 +17,7 @@ final class Ctx public function __construct( public readonly string $alias, public string $class, - public \Stringable|string|null $parameter = null, + public \Stringable|string|null $context = null, public ?bool $singleton = null, public ?\ReflectionClass $reflection = null, ) { diff --git a/src/Core/src/Internal/Resolver.php b/src/Core/src/Internal/Resolver.php index 55634f476..d956afd88 100644 --- a/src/Core/src/Internal/Resolver.php +++ b/src/Core/src/Internal/Resolver.php @@ -208,7 +208,7 @@ private function resolveParameter(ReflectionParameter $parameter, ResolvingState $types = $reflectionType instanceof ReflectionNamedType ? [$reflectionType] : $reflectionType->getTypes(); foreach ($types as $namedType) { try { - if (!$namedType->isBuiltin() && $this->resolveObject($state, $namedType, $parameter, $validate,)) { + if (!$namedType->isBuiltin() && $this->resolveObject($state, $namedType, $parameter, $validate)) { return true; } } catch (Throwable $e) { diff --git a/src/Core/tests/Fixtures/ExtendedContextInjector.php b/src/Core/tests/Fixtures/ExtendedContextInjector.php index 501cd3c76..4f9de0ffd 100644 --- a/src/Core/tests/Fixtures/ExtendedContextInjector.php +++ b/src/Core/tests/Fixtures/ExtendedContextInjector.php @@ -4,11 +4,11 @@ namespace Spiral\Tests\Core\Fixtures; -use DateTimeInterface; use Spiral\Core\Container\InjectorInterface; +use stdClass; /** - * @implements InjectorInterface + * @implements InjectorInterface */ class ExtendedContextInjector implements InjectorInterface { diff --git a/src/Core/tests/InjectableTest.php b/src/Core/tests/InjectableTest.php index eb9485f7b..e9f3c9ee9 100644 --- a/src/Core/tests/InjectableTest.php +++ b/src/Core/tests/InjectableTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; use ReflectionClass; use ReflectionMethod; +use Spiral\Core\Config\Injectable; use Spiral\Core\ConfigsInterface; use Spiral\Core\Container; use Spiral\Core\Exception\Container\AutowireException; @@ -203,6 +204,38 @@ public function testExtendedInjector(): void $this->assertInstanceOf(\ReflectionParameter::class, $result->context); } + public function testExtendedInjectorAnonClassObjectParam(): void + { + $container = new Container(); + $container->bind(stdClass::class, new Injectable(new class implements Container\InjectorInterface { + public function createInjection(\ReflectionClass $class, object|string|null $context = null): object + { + return (object)['context' => $context]; + } + })); + + $result = $container->invoke(fn(stdClass $dt) => $dt); + + $this->assertInstanceOf(stdClass::class, $result); + $this->assertInstanceOf(\ReflectionParameter::class, $result->context); + } + + public function testExtendedInjectorAnonClassMixedParam(): void + { + $container = new Container(); + $container->bind(stdClass::class, new Injectable(new class implements Container\InjectorInterface { + public function createInjection(\ReflectionClass $class, mixed $context = null): object + { + return (object)['context' => $context]; + } + })); + + $result = $container->invoke(fn(stdClass $dt) => $dt); + + $this->assertInstanceOf(stdClass::class, $result); + $this->assertInstanceOf(\ReflectionParameter::class, $result->context); + } + /** * @param TestConfig $contextArgument */ From 83760f60798ee12d0abd6e3fb1ec03448641003b Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 22 Dec 2023 18:11:41 +0000 Subject: [PATCH 3/6] Apply fixes from StyleCI [ci skip] [skip ci] --- src/Core/src/Internal/Factory.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/src/Internal/Factory.php b/src/Core/src/Internal/Factory.php index 276ca4aff..cedd62c81 100644 --- a/src/Core/src/Internal/Factory.php +++ b/src/Core/src/Internal/Factory.php @@ -140,7 +140,8 @@ private function resolveInjector(Injectable $binding, Ctx $ctx, array $arguments // todo wat should we do with arguments? /** @var array, \ReflectionMethod|false> $cache reflection for extended injectors */ static $cache = []; - $extended = $cache[$injectorInstance::class] ??= (static fn(\ReflectionType $type): bool => + $extended = $cache[$injectorInstance::class] ??= ( + static fn (\ReflectionType $type): bool => $type::class === \ReflectionUnionType::class || (string)$type === 'mixed' )( ($refMethod = new \ReflectionMethod($injectorInstance, 'createInjection')) From e03dac2b14740af0e56d9ce2d93d1cc96ca9dcd0 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Wed, 27 Dec 2023 17:32:55 +0400 Subject: [PATCH 4/6] Update tests --- src/Core/tests/ExceptionsTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Core/tests/ExceptionsTest.php b/src/Core/tests/ExceptionsTest.php index 74219eae8..586b45535 100644 --- a/src/Core/tests/ExceptionsTest.php +++ b/src/Core/tests/ExceptionsTest.php @@ -96,7 +96,7 @@ public function testExceptionTraceWithInvalidDependencyInSignature(): void signature: function (Psr\Container\ContainerInterface $container, Spiral\Tests\Core\Fixtures\InvalidClass $class) - action: 'autowire' alias: 'Spiral\Tests\Core\Fixtures\InvalidClass' - context: 'class' + context: Parameter #1 [ Spiral\Tests\Core\Fixtures\InvalidClass $class ] MARKDOWN, ); @@ -207,7 +207,7 @@ public static function exceptionTraceDataProvider(): \Traversable signature: function (Spiral\Tests\Core\Fixtures\InvalidClass $class) - action: 'autowire' alias: 'Spiral\Tests\Core\Fixtures\InvalidClass' - context: 'class' + context: Parameter #0 [ Spiral\Tests\Core\Fixtures\InvalidClass $class ] MARKDOWN ]; yield 'binding' => [ @@ -223,7 +223,7 @@ public static function exceptionTraceDataProvider(): \Traversable - action: 'resolve from binding' alias: 'Spiral\Tests\Core\Fixtures\InvalidClass' scope: 'root' - context: 'class' + context: Parameter #0 [ Spiral\Tests\Core\Fixtures\InvalidClass $class ] binding: Deferred factory 'invalid'->invalid() - action: 'autowire' alias: 'invalid' @@ -243,11 +243,11 @@ public static function exceptionTraceDataProvider(): \Traversable - action: 'resolve from binding' alias: 'Spiral\Tests\Core\Fixtures\InvalidClass' scope: 'root' - context: 'class' + context: Parameter #0 [ Spiral\Tests\Core\Fixtures\InvalidClass $class ] binding: Alias to `Spiral\Tests\Core\Fixtures\WithPrivateConstructor` - action: 'autowire' alias: 'Spiral\Tests\Core\Fixtures\WithPrivateConstructor' - context: 'class' + context: Parameter #0 [ Spiral\Tests\Core\Fixtures\InvalidClass $class ] MARKDOWN ]; yield 'withClosure' => [ @@ -275,7 +275,7 @@ public static function exceptionTraceDataProvider(): \Traversable - action: 'resolve from binding' alias: 'Spiral\Tests\Core\Fixtures\InvalidClass' scope: 'root' - context: 'class' + context: Parameter #0 [ Spiral\Tests\Core\Fixtures\InvalidClass $class ] binding: Factory from static function (Psr\Container\ContainerInterface $container) - action: 'autowire' alias: 'invalid' From e137672544b9bab7aeeeaf290ef42cb82029004d Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Wed, 27 Dec 2023 17:41:00 +0400 Subject: [PATCH 5/6] Fix psalm issues --- src/Core/src/Container/InjectorInterface.php | 3 +-- src/Core/src/Internal/Factory.php | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Core/src/Container/InjectorInterface.php b/src/Core/src/Container/InjectorInterface.php index bd8a591f5..30b05fc6b 100644 --- a/src/Core/src/Container/InjectorInterface.php +++ b/src/Core/src/Container/InjectorInterface.php @@ -22,12 +22,11 @@ interface InjectorInterface * Parameter reflection can be used for dynamic class constructing, for example it can define * database name or config section to be used to construct requested instance. * - * @param \ReflectionClass $class Request class type. + * @param \ReflectionClass $class Request class type. * @param string|null $context Parameter or alias name. * * @return TClass * - * @psalm-assert \ReflectionClass $class * @throws ContainerException */ public function createInjection(\ReflectionClass $class, ?string $context = null): object; diff --git a/src/Core/src/Internal/Factory.php b/src/Core/src/Internal/Factory.php index cedd62c81..a263231d0 100644 --- a/src/Core/src/Internal/Factory.php +++ b/src/Core/src/Internal/Factory.php @@ -113,6 +113,10 @@ public function make(string $alias, array $parameters = [], Stringable|string|nu } } + /** + * @psalm-suppress UnusedParam + * todo wat should we do with $arguments? + */ private function resolveInjector(Injectable $binding, Ctx $ctx, array $arguments) { $context = $ctx->context; @@ -137,7 +141,6 @@ private function resolveInjector(Injectable $binding, Ctx $ctx, array $arguments ); } - // todo wat should we do with arguments? /** @var array, \ReflectionMethod|false> $cache reflection for extended injectors */ static $cache = []; $extended = $cache[$injectorInstance::class] ??= ( From b80910f2440a8b9c4efca738752fbf7d631a9ea2 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 2 Jan 2024 13:39:19 +0400 Subject: [PATCH 6/6] Don't resolve injector if constructor arguments are passed --- src/Core/src/Internal/Factory.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Core/src/Internal/Factory.php b/src/Core/src/Internal/Factory.php index a263231d0..8a1b886c5 100644 --- a/src/Core/src/Internal/Factory.php +++ b/src/Core/src/Internal/Factory.php @@ -169,7 +169,7 @@ private function resolveInjector(Injectable $binding, Ctx $ctx, array $arguments return $instance; } finally { - $this->state->bindings[$reflection->getName()] ??= $binding; + $this->state->bindings[$ctx->class] ??= $binding; } } @@ -385,7 +385,7 @@ private function validateNewInstance( * @template TObject of object * * @param Ctx $ctx - * @param array $parameters Constructor parameters. + * @param array $arguments Constructor arguments. * * @return TObject * @@ -394,7 +394,7 @@ private function validateNewInstance( */ private function createInstance( Ctx $ctx, - array $parameters, + array $arguments, ): object { $class = $ctx->class; try { @@ -409,9 +409,9 @@ private function createInstance( throw new BadScopeException($scope, $class); } - //We have to construct class using external injector when we know exact context - if ($this->binder->hasInjector($class)) { - return $this->resolveInjector($this->state->bindings[$ctx->class], $ctx, $parameters); + // We have to construct class using external injector when we know the exact context + if ($arguments === [] && $this->binder->hasInjector($class)) { + return $this->resolveInjector($this->state->bindings[$ctx->class], $ctx, $arguments); } if (!$reflection->isInstantiable()) { @@ -431,7 +431,7 @@ private function createInstance( try { $this->tracer->push(false, action: 'resolve arguments', signature: $constructor); $this->tracer->push(true); - $arguments = $this->resolver->resolveArguments($constructor, $parameters); + $args = $this->resolver->resolveArguments($constructor, $arguments); } catch (ValidationException $e) { throw new ContainerException( $this->tracer->combineTraceMessage( @@ -448,9 +448,9 @@ private function createInstance( } try { // Using constructor with resolved arguments - $this->tracer->push(false, call: "$class::__construct", arguments: $arguments); + $this->tracer->push(false, call: "$class::__construct", arguments: $args); $this->tracer->push(true); - $instance = new $class(...$arguments); + $instance = new $class(...$args); } catch (\TypeError $e) { throw new WrongTypeException($constructor, $e); } finally {