1919use ReflectionException ;
2020use ReflectionNamedType ;
2121use ReflectionParameter ;
22+ use RuntimeException ;
2223use function class_exists ;
2324
2425/**
@@ -52,10 +53,11 @@ public function has(string $id): bool {
5253
5354 /**
5455 * @param ReflectionClass $class the class to instantiate
56+ * @param list<class-string> $chain
5557 * @return object the created class
5658 * @suppress PhanUndeclaredClassInstanceof
5759 */
58- private function buildClass (ReflectionClass $ class ): object {
60+ private function buildClass (ReflectionClass $ class, array $ chain ): object {
5961 $ constructor = $ class ->getConstructor ();
6062 if ($ constructor === null ) {
6163 /* No constructor, return a instance directly */
@@ -64,17 +66,20 @@ private function buildClass(ReflectionClass $class): object {
6466 if (PHP_VERSION_ID >= 80400 && self ::$ useLazyObjects && !$ class ->isInternal ()) {
6567 /* For PHP>=8.4, use a lazy ghost to delay constructor and dependency resolving */
6668 /** @psalm-suppress UndefinedMethod */
67- return $ class ->newLazyGhost (function (object $ object ) use ($ constructor ): void {
69+ return $ class ->newLazyGhost (function (object $ object ) use ($ constructor, $ chain ): void {
6870 /** @psalm-suppress DirectConstructorCall For lazy ghosts we have to call the constructor directly */
69- $ object ->__construct (...$ this ->buildClassConstructorParameters ($ constructor ));
71+ $ object ->__construct (...$ this ->buildClassConstructorParameters ($ constructor, $ chain ));
7072 });
7173 } else {
72- return $ class ->newInstanceArgs ($ this ->buildClassConstructorParameters ($ constructor ));
74+ return $ class ->newInstanceArgs ($ this ->buildClassConstructorParameters ($ constructor, $ chain ));
7375 }
7476 }
7577
76- private function buildClassConstructorParameters (\ReflectionMethod $ constructor ): array {
77- return array_map (function (ReflectionParameter $ parameter ) {
78+ /**
79+ * @param list<class-string> $chain
80+ */
81+ private function buildClassConstructorParameters (\ReflectionMethod $ constructor , array $ chain ): array {
82+ return array_map (function (ReflectionParameter $ parameter ) use ($ chain ) {
7883 $ parameterType = $ parameter ->getType ();
7984
8085 $ resolveName = $ parameter ->getName ();
@@ -87,7 +92,7 @@ private function buildClassConstructorParameters(\ReflectionMethod $constructor)
8792 try {
8893 $ builtIn = $ parameterType !== null && ($ parameterType instanceof ReflectionNamedType)
8994 && $ parameterType ->isBuiltin ();
90- return $ this ->query ($ resolveName , !$ builtIn );
95+ return $ this ->query ($ resolveName , !$ builtIn, $ chain );
9196 } catch (ContainerExceptionInterface $ e ) {
9297 // Service not found, use the default value when available
9398 if ($ parameter ->isDefaultValueAvailable ()) {
@@ -97,7 +102,7 @@ private function buildClassConstructorParameters(\ReflectionMethod $constructor)
97102 if ($ parameterType !== null && ($ parameterType instanceof ReflectionNamedType) && !$ parameterType ->isBuiltin ()) {
98103 $ resolveName = $ parameter ->getName ();
99104 try {
100- return $ this ->query ($ resolveName );
105+ return $ this ->query ($ resolveName, chain: $ chain );
101106 } catch (ContainerExceptionInterface $ e2 ) {
102107 // Pass null if typed and nullable
103108 if ($ parameter ->allowsNull () && ($ parameterType instanceof ReflectionNamedType)) {
@@ -114,12 +119,16 @@ private function buildClassConstructorParameters(\ReflectionMethod $constructor)
114119 }, $ constructor ->getParameters ());
115120 }
116121
117- public function resolve ($ name ) {
122+ /**
123+ * @inheritDoc
124+ * @param list<class-string> $chain
125+ */
126+ public function resolve ($ name , array $ chain = []) {
118127 $ baseMsg = 'Could not resolve ' . $ name . '! ' ;
119128 try {
120129 $ class = new ReflectionClass ($ name );
121130 if ($ class ->isInstantiable ()) {
122- return $ this ->buildClass ($ class );
131+ return $ this ->buildClass ($ class, $ chain );
123132 } else {
124133 throw new QueryException ($ baseMsg
125134 . ' Class can not be instantiated ' );
@@ -130,14 +139,22 @@ public function resolve($name) {
130139 }
131140 }
132141
133- public function query (string $ name , bool $ autoload = true ) {
142+ /**
143+ * @inheritDoc
144+ * @param list<class-string> $chain
145+ */
146+ public function query (string $ name , bool $ autoload = true , array $ chain = []) {
134147 $ name = $ this ->sanitizeName ($ name );
135148 if (isset ($ this ->container [$ name ])) {
136149 return $ this ->container [$ name ];
137150 }
138151
139152 if ($ autoload ) {
140- $ object = $ this ->resolve ($ name );
153+ if (in_array ($ name , $ chain , true )) {
154+ throw new RuntimeException ('Tried to query ' . $ name . ', but it is already in the chain: ' . implode (', ' , $ chain ));
155+ }
156+
157+ $ object = $ this ->resolve ($ name , array_merge ($ chain , [$ name ]));
141158 $ this ->registerService ($ name , function () use ($ object ) {
142159 return $ object ;
143160 });
0 commit comments