Test Failed
Pull Request — master (#1041)
by Aleksei
07:08
created

Factory::validateArguments()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 9
ccs 0
cts 0
cp 0
rs 10
cc 2
nc 2
nop 2
crap 6
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\Finalize;
12
use Spiral\Core\Attribute\Scope as ScopeAttribute;
13
use Spiral\Core\Attribute\Singleton;
14
use Spiral\Core\BinderInterface;
15
use Spiral\Core\Config\Injectable;
16
use Spiral\Core\Config\DeferredFactory;
17
use Spiral\Core\Container\InjectorInterface;
18
use Spiral\Core\Container\SingletonInterface;
19
use Spiral\Core\Exception\Container\AutowireException;
20
use Spiral\Core\Exception\Container\ContainerException;
21
use Spiral\Core\Exception\Container\InjectionException;
22
use Spiral\Core\Exception\Container\NotCallableException;
23
use Spiral\Core\Exception\Container\NotFoundException;
24
use Spiral\Core\Exception\Resolver\ValidationException;
25
use Spiral\Core\Exception\Resolver\WrongTypeException;
26
use Spiral\Core\Exception\Scope\BadScopeException;
27
use Spiral\Core\FactoryInterface;
28
use Spiral\Core\Internal\Common\DestructorTrait;
29
use Spiral\Core\Internal\Common\Registry;
30
use Spiral\Core\Internal\Factory\Ctx;
31
use Spiral\Core\InvokerInterface;
32
use Spiral\Core\ResolverInterface;
33
use Stringable;
34
use WeakReference;
35
36
/**
37
 * @internal
38
 */
39
final class Factory implements FactoryInterface
40
{
41
    use DestructorTrait;
42
43
    private State $state;
44
    private BinderInterface $binder;
45
    private InvokerInterface $invoker;
46
    private ContainerInterface $container;
47
    private ResolverInterface $resolver;
48
    private Tracer $tracer;
49 1286
    private Scope $scope;
50
51 1286
    public function __construct(Registry $constructor)
52
    {
53 1286
        $constructor->set('factory', $this);
54 1286
55 1286
        $this->state = $constructor->get('state', State::class);
56 1286
        $this->binder = $constructor->get('binder', BinderInterface::class);
57 1286
        $this->invoker = $constructor->get('invoker', InvokerInterface::class);
58 1286
        $this->container = $constructor->get('container', ContainerInterface::class);
59 1286
        $this->resolver = $constructor->get('resolver', ResolverInterface::class);
60
        $this->tracer = $constructor->get('tracer', Tracer::class);
61
        $this->scope = $constructor->get('scope', Scope::class);
62
    }
63
64
    /**
65
     * @param Stringable|string|null $context Related to parameter caused injection if any.
66
     *
67 1099
     * @throws \Throwable
68
     */
69 1099
    public function make(string $alias, array $parameters = [], Stringable|string|null $context = null): mixed
70 501
    {
71
        if ($parameters === [] && \array_key_exists($alias, $this->state->singletons)) {
72
            return $this->state->singletons[$alias];
73 1099
        }
74
75 1099
        $binding = $this->state->bindings[$alias] ?? null;
76 913
77
        if ($binding === null) {
78
            return $this->resolveWithoutBinding($alias, $parameters, $context);
79
        }
80 1024
81 1024
        try {
82 1024
            $this->tracer->push(
83 1024
                false,
84 1024
                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

84
                /** @scrutinizer ignore-type */ action: 'resolve from binding',
Loading history...
85 1024
                alias: $alias,
86 1024
                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

86
                /** @scrutinizer ignore-type */ scope: $this->scope->getScopeName(),
Loading history...
87 1024
                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

87
                /** @scrutinizer ignore-type */ context: $context,
Loading history...
88 1024
                binding: $binding,
89
            );
90 1024
            $this->tracer->push(true);
91 1024
92 1024
            unset($this->state->bindings[$alias]);
93 1024
            return match ($binding::class) {
94 1024
                \Spiral\Core\Config\Alias::class => $this->resolveAlias($binding, $alias, $context, $parameters),
95 1024
                \Spiral\Core\Config\Autowire::class => $this->resolveAutowire($binding, $alias, $context, $parameters),
96 1024
                DeferredFactory::class,
97 1024
                \Spiral\Core\Config\Factory::class => $this->resolveFactory($binding, $alias, $context, $parameters),
98 1024
                \Spiral\Core\Config\Shared::class => $this->resolveShared($binding, $alias, $context, $parameters),
99 1024
                Injectable::class => $this->resolveInjector(
100 1024
                    $binding,
101 1024
                    new Ctx(alias: $alias, class: $alias, context: $context),
102 1024
                    $parameters,
103
                ),
104 1024
                \Spiral\Core\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...
105 1024
                \Spiral\Core\Config\WeakReference::class => $this
106 1024
                    ->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

106
                    ->resolveWeakReference(/** @scrutinizer ignore-type */ $binding, $alias, $context, $parameters),
Loading history...
107
                default => $binding,
108
            };
109
        } finally {
110 469
            $this->state->bindings[$alias] ??= $binding;
111
            $this->tracer->pop(true);
112
            $this->tracer->pop(false);
113
        }
114
    }
115
116 469
    /**
117
     * @psalm-suppress UnusedParam
118
     * todo wat should we do with $arguments?
119 469
     */
120
    private function resolveInjector(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

120
    private function resolveInjector(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...
121
    {
122
        $context = $ctx->context;
123 469
        try {
124
            $reflection = $ctx->reflection ??= new \ReflectionClass($ctx->class);
125 469
        } catch (\ReflectionException $e) {
126
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
127
        }
128
129
        $injector = $binding->injector;
130 469
131
        try {
132
            $injectorInstance = \is_object($injector) ? $injector : $this->container->get($injector);
133 469
134
            if (!$injectorInstance instanceof InjectorInterface) {
135 462
                throw new InjectionException(
136 2
                    \sprintf(
137 2
                        "Class '%s' must be an instance of InjectorInterface for '%s'.",
138 2
                        $injectorInstance::class,
139 2
                        $reflection->getName()
140 2
                    )
141 2
                );
142 2
            }
143
144
            /** @var array<class-string<InjectorInterface>, \ReflectionMethod|false> $cache reflection for extended injectors */
145
            static $cache = [];
146
            $extended = $cache[$injectorInstance::class] ??= (
147
                static fn (\ReflectionType $type): bool =>
148 460
                $type::class === \ReflectionUnionType::class || (string)$type === 'mixed'
149 458
            )(
150 1
                ($refMethod = new \ReflectionMethod($injectorInstance, 'createInjection'))
151 1
                    ->getParameters()[1]->getType()
152 1
            ) ? $refMethod : false;
153 1
154 1
            $asIs = $extended && (\is_string($context) || $this->validateArguments($extended, [$reflection, $context]));
155 1
            $instance = $injectorInstance->createInjection($reflection, match (true) {
156
                $asIs => $context,
157
                $context instanceof ReflectionParameter => $context->getName(),
0 ignored issues
show
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

157
                $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...
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

157
                $context instanceof ReflectionParameter => $context->/** @scrutinizer ignore-call */ getName(),
Loading history...
158 457
                default => (string)$context,
159
            });
160 469
161
            if (!$reflection->isInstance($instance)) {
162
                throw new InjectionException(
163
                    \sprintf(
164 788
                        "Invalid injection response for '%s'.",
165
                        $reflection->getName()
166
                    )
167
                );
168
            }
169
170 788
            return $instance;
171 408
        } finally {
172 408
            $this->state->bindings[$reflection->getName()] ??= $binding;
173 408
        }
174 408
    }
175 788
176 780
    private function resolveAlias(
177
        \Spiral\Core\Config\Alias $binding,
178 785
        string $alias,
179 515
        Stringable|string|null $context,
180
        array $arguments,
181
    ): mixed {
182 785
        $result = $binding->alias === $alias
183
            ? $this->autowire(
184
                new Ctx(alias: $alias, class: $binding->alias, context: $context, singleton: $binding->singleton),
185 868
                $arguments,
186
            )
187
            //Binding is pointing to something else
188
            : $this->make($binding->alias, $arguments, $context);
189
190
        if ($binding->singleton && $arguments === []) {
191 868
            $this->state->singletons[$alias] = $result;
192 868
        }
193 1
194 1
        return $result;
195 1
    }
196 1
197 868
    private function resolveShared(
198
        \Spiral\Core\Config\Shared $binding,
199
        string $alias,
200 7
        Stringable|string|null $context,
201
        array $arguments,
202
    ): object {
203
        $avoidCache = $arguments !== [];
204
        return $avoidCache
205
            ? $this->createInstance(
206 7
                new Ctx(alias: $alias, class: $binding->value::class, context: $context),
207
                $arguments,
208 7
            )
209 7
            : $binding->value;
210
    }
211
212 480
    private function resolveAutowire(
213
        \Spiral\Core\Config\Autowire $binding,
214
        string $alias,
215
        Stringable|string|null $context,
216
        array $arguments,
217
    ): mixed {
218 480
        $instance = $binding->autowire->resolve($this, $arguments);
219
220 480
        $ctx = new Ctx(alias: $alias, class: $alias, context: $context, singleton: $binding->singleton);
221 14
        return $this->validateNewInstance($instance, $ctx, $arguments);
222 468
    }
223 8
224
    private function resolveFactory(
225
        \Spiral\Core\Config\Factory|DeferredFactory $binding,
226
        string $alias,
227
        Stringable|string|null $context,
228
        array $arguments,
229
    ): mixed {
230
        $ctx = new Ctx(alias: $alias, class: $alias, context: $context, singleton: $binding->singleton);
231 476
        try {
232
            $instance = $binding::class === \Spiral\Core\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

232
            $instance = $binding::class === \Spiral\Core\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...
233
                ? ($binding->factory)()
234 702
                : $this->invoker->invoke($binding->factory, $arguments);
235
        } catch (NotCallableException $e) {
236
            throw new ContainerException(
237
                $this->tracer->combineTraceMessage(\sprintf('Invalid binding for `%s`.', $ctx->alias)),
238
                $e->getCode(),
239
                $e,
240 702
            );
241
        }
242 702
243
        return \is_object($instance) ? $this->validateNewInstance($instance, $ctx, $arguments) : $instance;
244 3
    }
245
246 3
    private function resolveWeakReference(
247 3
        \Spiral\Core\Config\WeakReference $binding,
248 3
        string $alias,
249 3
        Stringable|string|null $context,
250 3
        array $arguments,
251 1
    ): ?object {
252
        $avoidCache = $arguments !== [];
253 2
254
        if (($avoidCache || $binding->reference->get() === null) && \class_exists($alias)) {
255
            try {
256
                $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

256
                $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

256
                $this->tracer->push(false, alias: $alias, source: WeakReference::class, /** @scrutinizer ignore-type */ context: $context);
Loading history...
257
258
                $object = $this->createInstance(
259
                    new Ctx(alias: $alias, class: $alias, context: $context),
260
                    $arguments,
261
                );
262
                if ($avoidCache) {
263
                    return $object;
264
                }
265 3
                $binding->reference = WeakReference::create($object);
266
            } catch (\Throwable) {
267
                throw new ContainerException(
268
                    $this->tracer->combineTraceMessage(
269 702
                        \sprintf(
270
                            'Can\'t resolve `%s`: can\'t instantiate `%s` from WeakReference binding.',
271
                            $this->tracer->getRootAlias(),
272 913
                            $alias,
273
                        )
274 913
                    )
275
                );
276 913
            } finally {
277
                $this->tracer->pop();
278 14
            }
279 14
        }
280 14
281 14
        return $binding->reference->get();
282 14
    }
283 3
284 3
    private function resolveWithoutBinding(
285 3
        string $alias,
286
        array $parameters = [],
287 1
        Stringable|string|null $context = null
288 1
    ): mixed {
289 1
        $parent = $this->scope->getParent();
290 1
291 1
        if ($parent !== null) {
292 1
            try {
293 1
                $this->tracer->push(false, ...[
294 1
                    'current scope' => $this->scope->getScopeName(),
295 1
                    'jump to parent scope' => $this->scope->getParentScope()->getScopeName(),
296
                ]);
297 14
                return $parent->make($alias, $parameters, $context);
298
            } catch (BadScopeException $e) {
299
                if ($this->scope->getScopeName() !== $e->getScope()) {
300
                    throw $e;
301 909
                }
302
            } catch (ContainerExceptionInterface $e) {
303
                $className = match (true) {
304 909
                    $e instanceof NotFoundException => NotFoundException::class,
305 909
                    default => ContainerException::class,
306 909
                };
307 909
                throw new $className($this->tracer->combineTraceMessage(\sprintf(
308
                    'Can\'t resolve `%s`.',
309 909
                    $alias,
310
                )), previous: $e);
311
            } finally {
312
                $this->tracer->pop(false);
313
            }
314
        }
315
316
        $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

316
        $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

316
        $this->tracer->push(false, action: 'autowire', alias: $alias, /** @scrutinizer ignore-type */ context: $context);
Loading history...
317
        try {
318
            //No direct instructions how to construct class, make is automatically
319
            return $this->autowire(
320
                new Ctx(alias: $alias, class: $alias, context: $context),
321
                $parameters,
322 912
            );
323
        } finally {
324 912
            $this->tracer->pop(false);
325 911
        }
326 911
    }
327 911
328
    /**
329
     * Automatically create class.
330 653
     * Object will be cached if the $arguments list is empty.
331 653
     *
332 653
     * @psalm-assert class-string $class
333 653
     *
334 653
     * @throws AutowireException
335
     * @throws \Throwable
336
     */
337
    private function autowire(Ctx $ctx, array $arguments): object
338 899
    {
339
        if (!(\class_exists($ctx->class) || (
340
            \interface_exists($ctx->class)
341 874
                &&
342 862
                (isset($this->state->injectors[$ctx->class]) || $this->binder->hasInjector($ctx->class))
343 874
        ))
344
        ) {
345
            throw new NotFoundException($this->tracer->combineTraceMessage(\sprintf(
346
                'Can\'t resolve `%s`: undefined class or binding `%s`.',
347
                $this->tracer->getRootAlias(),
348
                $ctx->class,
349
            )));
350 479
        }
351
352
        // automatically create instance
353
        $instance = $this->createInstance($ctx, $arguments);
354
355
        // apply registration functions to created instance
356 479
        return $arguments === []
357 479
            ? $this->registerInstance($ctx, $instance)
358 479
            : $instance;
359
    }
360
361
    /**
362 479
     * @throws BadScopeException
363 473
     * @throws \Throwable
364 479
     */
365
    private function validateNewInstance(
366
        object $instance,
367
        Ctx $ctx,
368
        array $arguments,
369
    ): object {
370
        // Check scope name
371
        $ctx->reflection = new \ReflectionClass($instance);
372
        $scopeName = ($ctx->reflection->getAttributes(ScopeAttribute::class)[0] ?? null)?->newInstance()->name;
373
        if ($scopeName !== null && $scopeName !== $this->scope->getScopeName()) {
374
            throw new BadScopeException($scopeName, $instance::class);
375
        }
376
377
        return $arguments === []
378
            ? $this->registerInstance($ctx, $instance)
379
            : $instance;
380 903
    }
381
382
    /**
383
     * Create instance of desired class.
384 903
     *
385
     * @template TObject of object
386 903
     *
387
     * @param Ctx<TObject> $ctx
388
     * @param array $parameters Constructor parameters.
389
     *
390
     * @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...
391
     *
392 903
     * @throws ContainerException
393 903
     * @throws \Throwable
394 4
     */
395
    private function createInstance(
396
        Ctx $ctx,
397
        array $parameters,
398 902
    ): object {
399 460
        $class = $ctx->class;
400
        try {
401
            $ctx->reflection = $reflection = new \ReflectionClass($class);
402 894
        } catch (\ReflectionException $e) {
403 4
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
404 4
        }
405 4
406 4
        // Check scope name
407 4
        $scope = ($reflection->getAttributes(ScopeAttribute::class)[0] ?? null)?->newInstance()->name;
408 4
        if ($scope !== null && $scope !== $this->scope->getScopeName()) {
409 4
            throw new BadScopeException($scope, $class);
410 4
        }
411
412
        //We have to construct class using external injector when we know exact context
413 893
        if ($this->binder->hasInjector($class)) {
414
            return $this->resolveInjector($this->state->bindings[$ctx->class], $ctx, $parameters);
415 893
        }
416
417 817
        if (!$reflection->isInstantiable()) {
418 817
            $itIs = match (true) {
419 817
                $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

419
                $reflection->/** @scrutinizer ignore-call */ 
420
                             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...
420 21
                $reflection->isAbstract() => 'Abstract class',
421 6
                default => 'Class',
422 6
            };
423 6
            throw new ContainerException(
424 6
                $this->tracer->combineTraceMessage(\sprintf('%s `%s` can not be constructed.', $itIs, $class)),
425 6
            );
426 6
        }
427 6
428 6
        $constructor = $reflection->getConstructor();
429 6
430
        if ($constructor !== null) {
431 817
            try {
432 817
                $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

432
                $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

432
                $this->tracer->push(false, /** @scrutinizer ignore-type */ action: 'resolve arguments', signature: $constructor);
Loading history...
433
                $this->tracer->push(true);
434
                $arguments = $this->resolver->resolveArguments($constructor, $parameters);
435
            } catch (ValidationException $e) {
436 797
                throw new ContainerException(
437 797
                    $this->tracer->combineTraceMessage(
438 797
                        \sprintf(
439 1
                            'Can\'t resolve `%s`. %s',
440
                            $this->tracer->getRootAlias(),
441
                            $e->getMessage()
442 797
                        )
443 797
                    ),
444
                );
445
            } finally {
446
                $this->tracer->pop(true);
447 710
                $this->tracer->pop(false);
448
            }
449
            try {
450 872
                // Using constructor with resolved arguments
451
                $this->tracer->push(false, call: "$class::__construct", arguments: $arguments);
452
                $this->tracer->push(true);
453
                $instance = new $class(...$arguments);
454
            } catch (\TypeError $e) {
455
                throw new WrongTypeException($constructor, $e);
456 872
            } finally {
457
                $this->tracer->pop(true);
458 872
                $this->tracer->pop(false);
459
            }
460 872
        } else {
461
            // No constructor specified
462
            $instance = $reflection->newInstance();
463 872
        }
464 548
465
        return $instance;
466
    }
467
468 872
    /**
469 872
     * Register instance in container, might perform methods like auto-singletons, log populations, etc.
470 4
     */
471
    private function registerInstance(Ctx $ctx, object $instance): object
472
    {
473 872
        $ctx->reflection ??= new \ReflectionClass($instance);
474
475
        $instance = $this->runInflector($instance);
476
477
        //Declarative singletons
478
        if ($this->isSingleton($ctx)) {
479 872
            $this->state->singletons[$ctx->alias] = $instance;
480
        }
481 872
482 475
        // Register finalizer
483
        $finalizer = $this->getFinalizer($ctx, $instance);
484
        if ($finalizer !== null) {
485
            $this->state->finalizers[] = $finalizer;
486 863
        }
487 517
488
        return $instance;
489
    }
490 859
491
    /**
492
     * Check the class was configured as a singleton.
493 872
     */
494
    private function isSingleton(Ctx $ctx): bool
495
    {
496
        if ($ctx->singleton === true) {
497
            return true;
498
        }
499 872
500 872
        /** @psalm-suppress RedundantCondition https://github.com/vimeo/psalm/issues/9489 */
501 871
        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

501
        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...
502
            return true;
503
        }
504 4
505
        return $ctx->reflection->getAttributes(Singleton::class) !== [];
506
    }
507
508
    private function getFinalizer(Ctx $ctx, object $instance): ?callable
509
    {
510 872
        /**
511
         * @psalm-suppress UnnecessaryVarAnnotation
512 872
         * @var Finalize|null $attribute
513
         */
514 872
        $attribute = ($ctx->reflection->getAttributes(Finalize::class)[0] ?? null)?->newInstance();
515 872
        if ($attribute === null) {
516 5
            return null;
517 5
        }
518 5
519 1
        return [$instance, $attribute->method];
520 4
    }
521
522
    /**
523
     * Find and run inflector
524
     */
525 872
    private function runInflector(object $instance): object
526
    {
527
        $scope = $this->scope;
528 872
529
        while ($scope !== null) {
530
            foreach ($this->state->inflectors as $class => $inflectors) {
531
                if ($instance instanceof $class) {
532
                    foreach ($inflectors as $inflector) {
533
                        $instance = $inflector->getParametersCount() > 1
534
                            ? $this->invoker->invoke($inflector->inflector, [$instance])
535
                            : ($inflector->inflector)($instance);
536
                    }
537
                }
538
            }
539
540
            $scope = $scope->getParentScope();
541
        }
542
543
        return $instance;
544
    }
545
546
    private function validateArguments(ContextFunction $reflection, array $arguments = []): bool
547
    {
548
        try {
549
            $this->resolver->validateArguments($reflection, $arguments);
550
        } catch (\Throwable) {
551
            return false;
552
        }
553
554
        return true;
555
    }
556
}
557