Skip to content

Commit cfbad57

Browse files
committed
Fix recursion in Container Proxy (#1140)
1 parent 4baf322 commit cfbad57

File tree

4 files changed

+60
-2
lines changed

4 files changed

+60
-2
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Spiral\Core\Exception\Container;
6+
7+
/**
8+
* Recursion can occur due to improper container configuration or
9+
* an unplanned exit from the scope by the execution thread.
10+
*/
11+
class RecursiveProxyException extends ContainerException
12+
{
13+
}

src/Core/src/Internal/Proxy.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,9 @@ public static function create(
8686

8787
return $instance;
8888
}
89+
90+
public static function isProxy(object $object): bool
91+
{
92+
return \in_array($object::class, self::$classes, true);
93+
}
8994
}

src/Core/src/Internal/Proxy/Resolver.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
use Psr\Container\ContainerInterface;
88
use Spiral\Core\ContainerScope;
99
use Spiral\Core\Exception\Container\ContainerException;
10+
use Spiral\Core\Exception\Container\RecursiveProxyException;
11+
use Spiral\Core\Internal\Introspector;
12+
use Spiral\Core\Internal\Proxy;
1013

1114
/**
1215
* @internal
@@ -16,7 +19,7 @@ final class Resolver
1619
public static function resolve(
1720
string $alias,
1821
\Stringable|string|null $context = null,
19-
?ContainerInterface $c = null
22+
?ContainerInterface $c = null,
2023
): object {
2124
$c ??= ContainerScope::getContainer() ?? throw new ContainerException('Proxy is out of scope.');
2225

@@ -27,11 +30,28 @@ public static function resolve(
2730
);
2831
} catch (\Throwable $e) {
2932
throw new ContainerException(
30-
\sprintf('Unable to resolve `%s` in a Proxy.', $alias),
33+
\sprintf('Unable to resolve `%s` in a Proxy in `%s` scope.', $alias, self::getScope($c)),
3134
previous: $e,
3235
);
3336
}
3437

38+
if (Proxy::isProxy($result)) {
39+
throw new RecursiveProxyException(
40+
\sprintf('Recursive proxy detected for `%s` in `%s` scope.', $alias, self::getScope($c)),
41+
);
42+
}
43+
3544
return $result;
3645
}
46+
47+
/**
48+
* @return non-empty-string
49+
*/
50+
private static function getScope(ContainerInterface $c): string
51+
{
52+
return \implode('.', \array_reverse(\array_map(
53+
static fn (?string $name): string => $name ?? 'null',
54+
Introspector::scopeNames($c),
55+
)));
56+
}
3757
}

src/Core/tests/Scope/ProxyTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Spiral\Core\Attribute\Proxy;
1010
use Spiral\Core\Container;
1111
use Spiral\Core\Container\InjectorInterface;
12+
use Spiral\Core\Exception\Container\RecursiveProxyException;
1213
use Spiral\Core\Scope;
1314
use Spiral\Tests\Core\Scope\Stub\Context;
1415
use Spiral\Tests\Core\Scope\Stub\ContextInterface;
@@ -296,6 +297,25 @@ public function __toString(): string
296297
);
297298
}
298299

300+
/**
301+
* Proxy gets a proxy of the same type.
302+
*/
303+
public function testRecursiveProxy(): void
304+
{
305+
$root = new Container();
306+
$root->bind(UserInterface::class, new \Spiral\Core\Config\Proxy(UserInterface::class));
307+
308+
$this->expectException(RecursiveProxyException::class);
309+
$this->expectExceptionMessage(
310+
'Recursive proxy detected for `Spiral\Tests\Core\Scope\Stub\UserInterface` in `root.null` scope.',
311+
);
312+
313+
$root->runScope(
314+
new Scope(),
315+
fn(#[Proxy] UserInterface $user) => $user->getName(),
316+
);
317+
}
318+
299319
/*
300320
// Proxy::$attachContainer=true tests
301321

0 commit comments

Comments
 (0)