diff --git a/src/Core/src/Internal/Factory.php b/src/Core/src/Internal/Factory.php index e786ce003..0992a7f57 100644 --- a/src/Core/src/Internal/Factory.php +++ b/src/Core/src/Internal/Factory.php @@ -381,8 +381,11 @@ private function validateNewInstance( // Check scope name $ctx->reflection = new \ReflectionClass($instance); $scopeName = ($ctx->reflection->getAttributes(Attribute\Scope::class)[0] ?? null)?->newInstance()->name; - if ($scopeName !== null && $scopeName !== $this->scope->getScopeName()) { - throw new BadScopeException($scopeName, $instance::class); + if ($scopeName !== null) { + $scope = $this->scope; + while ($scope->getScopeName() !== $scopeName) { + $scope = $scope->getParentScope() ?? throw new BadScopeException($scopeName, $instance::class); + } } return $arguments === [] diff --git a/src/Core/tests/Scope/ScopeAttributeTest.php b/src/Core/tests/Scope/ScopeAttributeTest.php index 7fbb684e6..8a4f14a5e 100644 --- a/src/Core/tests/Scope/ScopeAttributeTest.php +++ b/src/Core/tests/Scope/ScopeAttributeTest.php @@ -43,6 +43,33 @@ public function testNamedScopeResolveFromRootInNullScope(): void }, name: 'foo'); } + public function testNamedScopeResolveFromParentScope(): void + { + $root = new Container(); + $root->getBinder('bar')->bindSingleton('binding', static fn () => new AttrScopeFoo()); + + $root->runScoped(static function (Container $fooScope) { + $fooScope->runScoped(static function (Container $container) { + self::assertInstanceOf(AttrScopeFoo::class, $container->get('binding')); + }, name: 'bar'); + }, name: 'foo'); + } + + public function testBadScopeExceptionAllParentNamedScopesNotContainsNeededScope(): void + { + self::expectException(BadScopeException::class); + self::expectExceptionMessage('`foo`'); + + $root = new Container(); + $root->getBinder('bar')->bindSingleton('binding', static fn () => new AttrScopeFoo()); + + $root->runScoped(static function (Container $fooScope) { + $fooScope->runScoped(static function (Container $container) { + self::assertInstanceOf(AttrScopeFoo::class, $container->get('binding')); + }, name: 'bar'); + }, name: 'baz'); + } + /** * Request a dependency from a correct scope using alias but there is no any binding for this alias in the scope. * The binding can be in the parent scope, but it doesn't matter. diff --git a/src/Exceptions/src/Renderer/ConsoleRenderer.php b/src/Exceptions/src/Renderer/ConsoleRenderer.php index 61a1227ff..25487b378 100644 --- a/src/Exceptions/src/Renderer/ConsoleRenderer.php +++ b/src/Exceptions/src/Renderer/ConsoleRenderer.php @@ -41,7 +41,7 @@ class ConsoleRenderer extends AbstractRenderer private bool $colorsSupport; /** - * @param bool|resource $stream + * @param bool|resource|null $stream */ public function __construct(mixed $stream = null) { diff --git a/src/Security/src/RulesInterface.php b/src/Security/src/RulesInterface.php index 8bbea55b5..7c93b3d5f 100644 --- a/src/Security/src/RulesInterface.php +++ b/src/Security/src/RulesInterface.php @@ -23,9 +23,9 @@ interface RulesInterface * * Technically you can use this method as you use container bindings. * - * @param string $name Rule name in a string form. - * @param string|array|callable|RuleInterface $rule Rule, if kept as null rule name must be - * treated as class name for RuleInterface. + * @param string $name Rule name in a string form. + * @param string|array|callable|RuleInterface|null $rule Rule, if kept as null rule name must be treated as class + * name for RuleInterface. * @throws RuleException */ public function set(string $name, mixed $rule = null): self;