Passed
Push — master ( 72934f...dfb406 )
by Aleksei
05:59 queued 26s
created

Factory::resolveProxy()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 10
cc 2
nc 2
nop 3
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Core\Internal;
6
7
use Psr\Container\ContainerExceptionInterface;
8
use Psr\Container\ContainerInterface;
9
use ReflectionFunctionAbstract as ContextFunction;
10
use ReflectionParameter;
11
use Spiral\Core\Attribute;
12
use Spiral\Core\BinderInterface;
13
use Spiral\Core\Config;
14
use Spiral\Core\Container\InjectorInterface;
15
use Spiral\Core\Container\SingletonInterface;
16
use Spiral\Core\Exception\Container\AutowireException;
17
use Spiral\Core\Exception\Container\ContainerException;
18
use Spiral\Core\Exception\Container\InjectionException;
19
use Spiral\Core\Exception\Container\NotCallableException;
20
use Spiral\Core\Exception\Container\NotFoundException;
21
use Spiral\Core\Exception\Resolver\ValidationException;
22
use Spiral\Core\Exception\Resolver\WrongTypeException;
23
use Spiral\Core\Exception\Scope\BadScopeException;
24
use Spiral\Core\FactoryInterface;
25
use Spiral\Core\Internal\Common\DestructorTrait;
26
use Spiral\Core\Internal\Common\Registry;
27
use Spiral\Core\Internal\Factory\Ctx;
28
use Spiral\Core\InvokerInterface;
29
use Spiral\Core\ResolverInterface;
30
use Stringable;
31
use WeakReference;
32
33
/**
34
 * @internal
35
 */
36
final class Factory implements FactoryInterface
37
{
38
    use DestructorTrait;
39
40
    private State $state;
41
    private BinderInterface $binder;
42
    private InvokerInterface $invoker;
43
    private ContainerInterface $container;
44
    private ResolverInterface $resolver;
45
    private Tracer $tracer;
46
    private Scope $scope;
47
48 1311
    public function __construct(Registry $constructor)
49
    {
50 1311
        $constructor->set('factory', $this);
51
52 1311
        $this->state = $constructor->get('state', State::class);
53 1311
        $this->binder = $constructor->get('binder', BinderInterface::class);
54 1311
        $this->invoker = $constructor->get('invoker', InvokerInterface::class);
55 1311
        $this->container = $constructor->get('container', ContainerInterface::class);
56 1311
        $this->resolver = $constructor->get('resolver', ResolverInterface::class);
57 1311
        $this->tracer = $constructor->get('tracer', Tracer::class);
58 1311
        $this->scope = $constructor->get('scope', Scope::class);
59
    }
60
61
    /**
62
     * @param Stringable|string|null $context Related to parameter caused injection if any.
63
     *
64
     * @throws \Throwable
65
     */
66 1119
    public function make(string $alias, array $parameters = [], Stringable|string|null $context = null): mixed
67
    {
68 1119
        if ($parameters === [] && \array_key_exists($alias, $this->state->singletons)) {
69 505
            return $this->state->singletons[$alias];
70
        }
71
72 1119
        $binding = $this->state->bindings[$alias] ?? null;
73
74 1119
        if ($binding === null) {
75 925
            return $this->resolveWithoutBinding($alias, $parameters, $context);
76
        }
77
78
        try {
79 1044
            $this->tracer->push(
80 1044
                false,
81 1044
                action: 'resolve from binding',
0 ignored issues
show
Bug introduced by
'resolve from binding' of type string is incompatible with the type boolean expected by parameter $nextLevel of Spiral\Core\Internal\Tracer::push(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

81
                /** @scrutinizer ignore-type */ action: 'resolve from binding',
Loading history...
82 1044
                alias: $alias,
83 1044
                scope: $this->scope->getScopeName(),
0 ignored issues
show
Bug introduced by
$this->scope->getScopeName() of type null|string is incompatible with the type boolean expected by parameter $nextLevel of Spiral\Core\Internal\Tracer::push(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

83
                /** @scrutinizer ignore-type */ scope: $this->scope->getScopeName(),
Loading history...
84 1044
                context: $context,
0 ignored issues
show
Bug introduced by
$context of type Stringable|null|string is incompatible with the type boolean expected by parameter $nextLevel of Spiral\Core\Internal\Tracer::push(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

84
                /** @scrutinizer ignore-type */ context: $context,
Loading history...
85 1044
                binding: $binding,
86 1044
            );
87 1044
            $this->tracer->push(true);
88
89 1044
            unset($this->state->bindings[$alias]);
90 1044
            return match ($binding::class) {
91 1044
                Config\Alias::class => $this->resolveAlias($binding, $alias, $context, $parameters),
92 1044
                Config\Proxy::class,
93 1044
                Config\DeprecationProxy::class => $this->resolveProxy($binding, $alias, $context),
94 1044
                Config\Autowire::class => $this->resolveAutowire($binding, $alias, $context, $parameters),
95 1044
                Config\DeferredFactory::class,
96 1044
                Config\Factory::class => $this->resolveFactory($binding, $alias, $context, $parameters),
97 1044
                Config\Shared::class => $this->resolveShared($binding, $alias, $context, $parameters),
98 1044
                Config\Injectable::class => $this->resolveInjector(
99 1044
                    $binding,
100 1044
                    new Ctx(alias: $alias, class: $alias, context: $context),
101 1044
                    $parameters,
102 1044
                ),
103 1044
                Config\Scalar::class => $binding->value,
0 ignored issues
show
Bug introduced by
The property value does not seem to exist on Spiral\Core\Config\Injectable.
Loading history...
104 1044
                Config\WeakReference::class => $this
105 1044
                    ->resolveWeakReference($binding, $alias, $context, $parameters),
0 ignored issues
show
Bug introduced by
$binding of type Spiral\Core\Config\Injectable is incompatible with the type Spiral\Core\Config\WeakReference expected by parameter $binding of Spiral\Core\Internal\Fac...:resolveWeakReference(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

105
                    ->resolveWeakReference(/** @scrutinizer ignore-type */ $binding, $alias, $context, $parameters),
Loading history...
106 1044
                default => $binding,
107 1044
            };
108
        } finally {
109 1044
            $this->state->bindings[$alias] ??= $binding;
110 1044
            $this->tracer->pop(true);
111 1044
            $this->tracer->pop(false);
112
        }
113
    }
114
115
    /**
116
     * @psalm-suppress UnusedParam
117
     * todo wat should we do with $arguments?
118
     */
119 468
    private function resolveInjector(Config\Injectable $binding, Ctx $ctx, array $arguments)
0 ignored issues
show
Unused Code introduced by
The parameter $arguments is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

119
    private function resolveInjector(Config\Injectable $binding, Ctx $ctx, /** @scrutinizer ignore-unused */ array $arguments)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
120
    {
121 468
        $context = $ctx->context;
122
        try {
123 468
            $reflection = $ctx->reflection ??= new \ReflectionClass($ctx->class);
124
        } catch (\ReflectionException $e) {
125
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
126
        }
127
128 468
        $injector = $binding->injector;
129
130
        try {
131 468
            $injectorInstance = \is_object($injector) ? $injector : $this->container->get($injector);
132
133 461
            if (!$injectorInstance instanceof InjectorInterface) {
134 2
                throw new InjectionException(
135 2
                    \sprintf(
136 2
                        "Class '%s' must be an instance of InjectorInterface for '%s'.",
137 2
                        $injectorInstance::class,
138 2
                        $reflection->getName()
139 2
                    )
140 2
                );
141
            }
142
143
            /** @var array<class-string<InjectorInterface>, \ReflectionMethod|false> $cache reflection for extended injectors */
144 459
            static $cache = [];
145 459
            $extended = $cache[$injectorInstance::class] ??= (
146 459
                static fn (\ReflectionType $type): bool =>
147 18
                $type::class === \ReflectionUnionType::class || (string)$type === 'mixed'
148 459
            )(
149 459
                ($refMethod = new \ReflectionMethod($injectorInstance, 'createInjection'))
150 459
                    ->getParameters()[1]->getType()
151 459
            ) ? $refMethod : false;
152
153 459
            $asIs = $extended && (\is_string($context) || $this->validateArguments($extended, [$reflection, $context]));
154 459
            $instance = $injectorInstance->createInjection($reflection, match (true) {
155 459
                $asIs => $context,
156 459
                $context instanceof ReflectionParameter => $context->getName(),
0 ignored issues
show
Bug introduced by
The method getName() does not exist on Stringable. It seems like you code against a sub-type of Stringable such as Spiral\Reactor\Partial\Method or Spiral\Reactor\AbstractDeclaration or Spiral\Reactor\FunctionDeclaration or Spiral\Reactor\Partial\PhpNamespace or SimpleXMLElement or SimpleXMLIterator or Spiral\Cookies\Cookie or ReflectionNamedType or PhpCsFixer\Console\Comma...beNameNotFoundException or ReflectionExtension or ReflectionProperty or ReflectionFunctionAbstract or ReflectionClassConstant or ReflectionClass or ReflectionZendExtension or ReflectionParameter or MonorepoBuilder202206\Ne...erators\CachingIterator or RectorPrefix202308\Nette\Iterators\CachingIterator or Nette\Iterators\CachingIterator. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

156
                $context instanceof ReflectionParameter => $context->/** @scrutinizer ignore-call */ getName(),
Loading history...
Bug introduced by
The method getName() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

156
                $context instanceof ReflectionParameter => $context->/** @scrutinizer ignore-call */ getName(),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
157 459
                default => (string)$context,
158 459
            });
159
160 457
            if (!$reflection->isInstance($instance)) {
161 1
                throw new InjectionException(
162 1
                    \sprintf(
163 1
                        "Invalid injection response for '%s'.",
164 1
                        $reflection->getName()
165 1
                    )
166 1
                );
167
            }
168
169 456
            return $instance;
170
        } finally {
171 468
            $this->state->bindings[$ctx->class] ??= $binding;
172
        }
173
    }
174
175 798
    private function resolveAlias(
176
        Config\Alias $binding,
177
        string $alias,
178
        Stringable|string|null $context,
179
        array $arguments,
180
    ): mixed {
181 798
        $result = $binding->alias === $alias
182 402
            ? $this->autowire(
183 402
                new Ctx(alias: $alias, class: $binding->alias, context: $context, singleton: $binding->singleton),
184 402
                $arguments,
185 402
            )
186 798
            //Binding is pointing to something else
187 790
            : $this->make($binding->alias, $arguments, $context);
188
189 795
        if ($binding->singleton && $arguments === []) {
190 522
            $this->state->singletons[$alias] = $result;
191
        }
192
193 795
        return $result;
194
    }
195
196 4
    private function resolveProxy(Config\Proxy $binding, string $alias, Stringable|string|null $context): mixed
197
    {
198 4
        $result = Proxy::create(new \ReflectionClass($binding->getInterface()), $context, new Attribute\Proxy());
199
200 4
        if ($binding->singleton) {
201 3
            $this->state->singletons[$alias] = $result;
202
        }
203
204 4
        return $result;
205
    }
206
207 862
    private function resolveShared(
208
        Config\Shared $binding,
209
        string $alias,
210
        Stringable|string|null $context,
211
        array $arguments,
212
    ): object {
213 862
        $avoidCache = $arguments !== [];
214 862
        return $avoidCache
215 1
            ? $this->createInstance(
216 1
                new Ctx(alias: $alias, class: $binding->value::class, context: $context),
217 1
                $arguments,
218 1
            )
219 862
            : $binding->value;
220
    }
221
222 7
    private function resolveAutowire(
223
        Config\Autowire $binding,
224
        string $alias,
225
        Stringable|string|null $context,
226
        array $arguments,
227
    ): mixed {
228 7
        $instance = $binding->autowire->resolve($this, $arguments);
229
230 7
        $ctx = new Ctx(alias: $alias, class: $alias, context: $context, singleton: $binding->singleton);
231 7
        return $this->validateNewInstance($instance, $ctx, $arguments);
232
    }
233
234 476
    private function resolveFactory(
235
        Config\Factory|Config\DeferredFactory $binding,
236
        string $alias,
237
        Stringable|string|null $context,
238
        array $arguments,
239
    ): mixed {
240 476
        $ctx = new Ctx(alias: $alias, class: $alias, context: $context, singleton: $binding->singleton);
241
        try {
242 476
            $instance = $binding::class === Config\Factory::class && $binding->getParametersCount() === 0
0 ignored issues
show
Bug introduced by
The method getParametersCount() does not exist on Spiral\Core\Config\DeferredFactory. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

242
            $instance = $binding::class === Config\Factory::class && $binding->/** @scrutinizer ignore-call */ getParametersCount() === 0

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
243 16
                ? ($binding->factory)()
244 462
                : $this->invoker->invoke($binding->factory, $arguments);
245 8
        } catch (NotCallableException $e) {
246
            throw new ContainerException(
247
                $this->tracer->combineTraceMessage(\sprintf('Invalid binding for `%s`.', $ctx->alias)),
248
                $e->getCode(),
249
                $e,
250
            );
251
        }
252
253 472
        return \is_object($instance) ? $this->validateNewInstance($instance, $ctx, $arguments) : $instance;
254
    }
255
256 704
    private function resolveWeakReference(
257
        Config\WeakReference $binding,
258
        string $alias,
259
        Stringable|string|null $context,
260
        array $arguments,
261
    ): ?object {
262 704
        $avoidCache = $arguments !== [];
263
264 704
        if (($avoidCache || $binding->reference->get() === null) && \class_exists($alias)) {
265
            try {
266 3
                $this->tracer->push(false, alias: $alias, source: WeakReference::class, context: $context);
0 ignored issues
show
Bug introduced by
$alias of type string is incompatible with the type boolean expected by parameter $nextLevel of Spiral\Core\Internal\Tracer::push(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

266
                $this->tracer->push(false, /** @scrutinizer ignore-type */ alias: $alias, source: WeakReference::class, context: $context);
Loading history...
Bug introduced by
$context of type Stringable|null|string is incompatible with the type boolean expected by parameter $nextLevel of Spiral\Core\Internal\Tracer::push(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

266
                $this->tracer->push(false, alias: $alias, source: WeakReference::class, /** @scrutinizer ignore-type */ context: $context);
Loading history...
267
268 3
                $object = $this->createInstance(
269 3
                    new Ctx(alias: $alias, class: $alias, context: $context),
270 3
                    $arguments,
271 3
                );
272 3
                if ($avoidCache) {
273 1
                    return $object;
274
                }
275 2
                $binding->reference = WeakReference::create($object);
276
            } catch (\Throwable) {
277
                throw new ContainerException(
278
                    $this->tracer->combineTraceMessage(
279
                        \sprintf(
280
                            'Can\'t resolve `%s`: can\'t instantiate `%s` from WeakReference binding.',
281
                            $this->tracer->getRootAlias(),
282
                            $alias,
283
                        )
284
                    )
285
                );
286
            } finally {
287 3
                $this->tracer->pop();
288
            }
289
        }
290
291 704
        return $binding->reference->get();
292
    }
293
294 925
    private function resolveWithoutBinding(
295
        string $alias,
296
        array $parameters = [],
297
        Stringable|string|null $context = null
298
    ): mixed {
299 925
        $parent = $this->scope->getParentFactory();
300
301 925
        if ($parent !== null) {
302
            try {
303 24
                $this->tracer->push(false, ...[
304 24
                    'current scope' => $this->scope->getScopeName(),
305 24
                    'jump to parent scope' => $this->scope->getParentScope()->getScopeName(),
306 24
                ]);
307
                /** @psalm-suppress TooManyArguments */
308 24
                return $parent->make($alias, $parameters, $context);
0 ignored issues
show
Unused Code introduced by
The call to Spiral\Core\FactoryInterface::make() has too many arguments starting with $context. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

308
                return $parent->/** @scrutinizer ignore-call */ make($alias, $parameters, $context);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
309 3
            } catch (BadScopeException $e) {
310 3
                if ($this->scope->getScopeName() !== $e->getScope()) {
311 3
                    throw $e;
312
                }
313 1
            } catch (ContainerExceptionInterface $e) {
314 1
                $className = match (true) {
315 1
                    $e instanceof NotFoundException => NotFoundException::class,
316 1
                    default => ContainerException::class,
317 1
                };
318 1
                throw new $className($this->tracer->combineTraceMessage(\sprintf(
319 1
                    'Can\'t resolve `%s`.',
320 1
                    $alias,
321 1
                )), previous: $e);
322
            } finally {
323 24
                $this->tracer->pop(false);
324
            }
325
        }
326
327 920
        $this->tracer->push(false, action: 'autowire', alias: $alias, context: $context);
0 ignored issues
show
Bug introduced by
'autowire' of type string is incompatible with the type boolean expected by parameter $nextLevel of Spiral\Core\Internal\Tracer::push(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

327
        $this->tracer->push(false, /** @scrutinizer ignore-type */ action: 'autowire', alias: $alias, context: $context);
Loading history...
Bug introduced by
$context of type Stringable|null|string is incompatible with the type boolean expected by parameter $nextLevel of Spiral\Core\Internal\Tracer::push(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

327
        $this->tracer->push(false, action: 'autowire', alias: $alias, /** @scrutinizer ignore-type */ context: $context);
Loading history...
328
        try {
329
            //No direct instructions how to construct class, make is automatically
330 920
            return $this->autowire(
331 920
                new Ctx(alias: $alias, class: $alias, context: $context),
332 920
                $parameters,
333 920
            );
334
        } finally {
335 920
            $this->tracer->pop(false);
336
        }
337
    }
338
339
    /**
340
     * Automatically create class.
341
     * Object will be cached if the $arguments list is empty.
342
     *
343
     * @psalm-assert class-string $class
344
     *
345
     * @throws AutowireException
346
     * @throws \Throwable
347
     */
348 923
    private function autowire(Ctx $ctx, array $arguments): object
349
    {
350 923
        if (!(\class_exists($ctx->class) || (
351 922
            \interface_exists($ctx->class)
352 922
                &&
353 922
                (isset($this->state->injectors[$ctx->class]) || $this->binder->hasInjector($ctx->class))
354
        ))
355
        ) {
356 646
            throw new NotFoundException($this->tracer->combineTraceMessage(\sprintf(
357 646
                'Can\'t resolve `%s`: undefined class or binding `%s`.',
358 646
                $this->tracer->getRootAlias(),
359 646
                $ctx->class,
360 646
            )));
361
        }
362
363
        // automatically create instance
364 910
        $instance = $this->createInstance($ctx, $arguments);
365
366
        // apply registration functions to created instance
367 885
        return $arguments === []
368 873
            ? $this->registerInstance($ctx, $instance)
369 885
            : $instance;
370
    }
371
372
    /**
373
     * @throws BadScopeException
374
     * @throws \Throwable
375
     */
376 472
    private function validateNewInstance(
377
        object $instance,
378
        Ctx $ctx,
379
        array $arguments,
380
    ): object {
381
        // Check scope name
382 472
        $ctx->reflection = new \ReflectionClass($instance);
383 472
        $scopeName = ($ctx->reflection->getAttributes(Attribute\Scope::class)[0] ?? null)?->newInstance()->name;
384 472
        if ($scopeName !== null && $scopeName !== $this->scope->getScopeName()) {
385
            throw new BadScopeException($scopeName, $instance::class);
386
        }
387
388 472
        return $arguments === []
389 466
            ? $this->registerInstance($ctx, $instance)
390 472
            : $instance;
391
    }
392
393
    /**
394
     * Create instance of desired class.
395
     *
396
     * @template TObject of object
397
     *
398
     * @param Ctx<TObject> $ctx
399
     * @param array $arguments Constructor arguments.
400
     *
401
     * @return TObject
0 ignored issues
show
Bug introduced by
The type Spiral\Core\Internal\TObject was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
402
     *
403
     * @throws ContainerException
404
     * @throws \Throwable
405
     */
406 914
    private function createInstance(
407
        Ctx $ctx,
408
        array $arguments,
409
    ): object {
410 914
        $class = $ctx->class;
411
        try {
412 914
            $ctx->reflection = $reflection = new \ReflectionClass($class);
413
        } catch (\ReflectionException $e) {
414
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
415
        }
416
417
        // Check scope name
418 914
        $scope = ($reflection->getAttributes(Attribute\Scope::class)[0] ?? null)?->newInstance()->name;
419 914
        if ($scope !== null && $scope !== $this->scope->getScopeName()) {
420 4
            throw new BadScopeException($scope, $class);
421
        }
422
423
        // We have to construct class using external injector when we know the exact context
424 913
        if ($arguments === [] && $this->binder->hasInjector($class)) {
425 454
            return $this->resolveInjector($this->state->bindings[$ctx->class], $ctx, $arguments);
426
        }
427
428 905
        if (!$reflection->isInstantiable()) {
429 8
            $itIs = match (true) {
430 8
                $reflection->isEnum() => 'Enum',
0 ignored issues
show
Bug introduced by
The method isEnum() does not exist on ReflectionClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

430
                $reflection->/** @scrutinizer ignore-call */ 
431
                             isEnum() => 'Enum',

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
431 8
                $reflection->isAbstract() => 'Abstract class',
432 8
                default => 'Class',
433 8
            };
434 8
            throw new ContainerException(
435 8
                $this->tracer->combineTraceMessage(\sprintf('%s `%s` can not be constructed.', $itIs, $class)),
436 8
            );
437
        }
438
439 904
        $constructor = $reflection->getConstructor();
440
441 904
        if ($constructor !== null) {
442
            try {
443 815
                $this->tracer->push(false, action: 'resolve arguments', signature: $constructor);
0 ignored issues
show
Bug introduced by
$constructor of type ReflectionMethod is incompatible with the type boolean expected by parameter $nextLevel of Spiral\Core\Internal\Tracer::push(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

443
                $this->tracer->push(false, action: 'resolve arguments', /** @scrutinizer ignore-type */ signature: $constructor);
Loading history...
Bug introduced by
'resolve arguments' of type string is incompatible with the type boolean expected by parameter $nextLevel of Spiral\Core\Internal\Tracer::push(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

443
                $this->tracer->push(false, /** @scrutinizer ignore-type */ action: 'resolve arguments', signature: $constructor);
Loading history...
444 815
                $this->tracer->push(true);
445 815
                $args = $this->resolver->resolveArguments($constructor, $arguments);
446 21
            } catch (ValidationException $e) {
447 6
                throw new ContainerException(
448 6
                    $this->tracer->combineTraceMessage(
449 6
                        \sprintf(
450 6
                            'Can\'t resolve `%s`. %s',
451 6
                            $this->tracer->getRootAlias(),
452 6
                            $e->getMessage()
453 6
                        )
454 6
                    ),
455 6
                );
456
            } finally {
457 815
                $this->tracer->pop(true);
458 815
                $this->tracer->pop(false);
459
            }
460
            try {
461
                // Using constructor with resolved arguments
462 795
                $this->tracer->push(false, call: "$class::__construct", arguments: $args);
463 795
                $this->tracer->push(true);
464 795
                $instance = new $class(...$args);
465 1
            } catch (\TypeError $e) {
466
                throw new WrongTypeException($constructor, $e);
467
            } finally {
468 795
                $this->tracer->pop(true);
469 795
                $this->tracer->pop(false);
470
            }
471
        } else {
472
            // No constructor specified
473 719
            $instance = $reflection->newInstance();
474
        }
475
476 883
        return $instance;
477
    }
478
479
    /**
480
     * Register instance in container, might perform methods like auto-singletons, log populations, etc.
481
     */
482 883
    private function registerInstance(Ctx $ctx, object $instance): object
483
    {
484 883
        $ctx->reflection ??= new \ReflectionClass($instance);
485
486 883
        $instance = $this->runInflector($instance);
487
488
        //Declarative singletons
489 883
        if ($this->isSingleton($ctx)) {
490 542
            $this->state->singletons[$ctx->alias] = $instance;
491
        }
492
493
        // Register finalizer
494 883
        $finalizer = $this->getFinalizer($ctx, $instance);
495 883
        if ($finalizer !== null) {
496 4
            $this->state->finalizers[] = $finalizer;
497
        }
498
499 883
        return $instance;
500
    }
501
502
    /**
503
     * Check the class was configured as a singleton.
504
     */
505 883
    private function isSingleton(Ctx $ctx): bool
506
    {
507 883
        if ($ctx->singleton === true) {
508 469
            return true;
509
        }
510
511
        /** @psalm-suppress RedundantCondition https://github.com/vimeo/psalm/issues/9489 */
512 874
        if ($ctx->reflection->implementsInterface(SingletonInterface::class)) {
0 ignored issues
show
Bug introduced by
The method implementsInterface() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

512
        if ($ctx->reflection->/** @scrutinizer ignore-call */ implementsInterface(SingletonInterface::class)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
513
            return true;
514
        }
515
516 874
        return $ctx->reflection->getAttributes(Attribute\Singleton::class) !== [];
517
    }
518
519 883
    private function getFinalizer(Ctx $ctx, object $instance): ?callable
520
    {
521
        /**
522
         * @psalm-suppress UnnecessaryVarAnnotation
523
         * @var Attribute\Finalize|null $attribute
524
         */
525 883
        $attribute = ($ctx->reflection->getAttributes(Attribute\Finalize::class)[0] ?? null)?->newInstance();
526 883
        if ($attribute === null) {
527 882
            return null;
528
        }
529
530 4
        return [$instance, $attribute->method];
531
    }
532
533
    /**
534
     * Find and run inflector
535
     */
536 883
    private function runInflector(object $instance): object
537
    {
538 883
        $scope = $this->scope;
539
540 883
        while ($scope !== null) {
541 883
            foreach ($this->state->inflectors as $class => $inflectors) {
542 5
                if ($instance instanceof $class) {
543 5
                    foreach ($inflectors as $inflector) {
544 5
                        $instance = $inflector->getParametersCount() > 1
545 1
                            ? $this->invoker->invoke($inflector->inflector, [$instance])
546 4
                            : ($inflector->inflector)($instance);
547
                    }
548
                }
549
            }
550
551 883
            $scope = $scope->getParentScope();
552
        }
553
554 883
        return $instance;
555
    }
556
557 5
    private function validateArguments(ContextFunction $reflection, array $arguments = []): bool
558
    {
559
        try {
560 5
            $this->resolver->validateArguments($reflection, $arguments);
561
        } catch (\Throwable) {
562
            return false;
563
        }
564
565 5
        return true;
566
    }
567
}
568