Test Failed
Pull Request — master (#1045)
by
unknown
12:32 queued 06:03
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 1288
    private Scope $scope;
50
51 1288
    public function __construct(Registry $constructor)
52
    {
53 1288
        $constructor->set('factory', $this);
54 1288
55 1288
        $this->state = $constructor->get('state', State::class);
56 1288
        $this->binder = $constructor->get('binder', BinderInterface::class);
57 1288
        $this->invoker = $constructor->get('invoker', InvokerInterface::class);
58 1288
        $this->container = $constructor->get('container', ContainerInterface::class);
59 1288
        $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 1101
     * @throws \Throwable
68
     */
69 1101
    public function make(string $alias, array $parameters = [], Stringable|string|null $context = null): mixed
70 503
    {
71
        if ($parameters === [] && \array_key_exists($alias, $this->state->singletons)) {
72
            return $this->state->singletons[$alias];
73 1101
        }
74
75 1101
        $binding = $this->state->bindings[$alias] ?? null;
76 915
77
        if ($binding === null) {
78
            return $this->resolveWithoutBinding($alias, $parameters, $context);
79
        }
80 1026
81 1026
        try {
82 1026
            $this->tracer->push(
83 1026
                false,
84 1026
                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 1026
                alias: $alias,
86 1026
                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 1026
                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 1026
                binding: $binding,
89
            );
90 1026
            $this->tracer->push(true);
91 1026
92 1026
            unset($this->state->bindings[$alias]);
93 1026
            return match ($binding::class) {
94 1026
                \Spiral\Core\Config\Alias::class => $this->resolveAlias($binding, $alias, $context, $parameters),
95 1026
                \Spiral\Core\Config\Autowire::class => $this->resolveAutowire($binding, $alias, $context, $parameters),
96 1026
                DeferredFactory::class,
97 1026
                \Spiral\Core\Config\Factory::class => $this->resolveFactory($binding, $alias, $context, $parameters),
98 1026
                \Spiral\Core\Config\Shared::class => $this->resolveShared($binding, $alias, $context, $parameters),
99 1026
                Injectable::class => $this->resolveInjector(
100 1026
                    $binding,
101 1026
                    new Ctx(alias: $alias, class: $alias, context: $context),
102 1026
                    $parameters,
103
                ),
104 1026
                \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 1026
                \Spiral\Core\Config\WeakReference::class => $this
106 1026
                    ->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 471
            $this->state->bindings[$alias] ??= $binding;
111
            $this->tracer->pop(true);
112
            $this->tracer->pop(false);
113
        }
114
    }
115
116 471
    /**
117
     * @psalm-suppress UnusedParam
118
     * todo wat should we do with $arguments?
119 471
     */
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 471
        try {
124
            $reflection = $ctx->reflection ??= new \ReflectionClass($ctx->class);
125 471
        } catch (\ReflectionException $e) {
126
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
127
        }
128
129
        $injector = $binding->injector;
130 471
131
        try {
132
            $injectorInstance = \is_object($injector) ? $injector : $this->container->get($injector);
133 471
134
            if (!$injectorInstance instanceof InjectorInterface) {
135 464
                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 462
                $type::class === \ReflectionUnionType::class || (string)$type === 'mixed'
149 460
            )(
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 459
                default => (string)$context,
159
            });
160 471
161
            if (!$reflection->isInstance($instance)) {
162
                throw new InjectionException(
163
                    \sprintf(
164 790
                        "Invalid injection response for '%s'.",
165
                        $reflection->getName()
166
                    )
167
                );
168
            }
169
170 790
            return $instance;
171 410
        } finally {
172 410
            $this->state->bindings[$reflection->getName()] ??= $binding;
173 410
        }
174 410
    }
175 790
176 782
    private function resolveAlias(
177
        \Spiral\Core\Config\Alias $binding,
178 787
        string $alias,
179 517
        Stringable|string|null $context,
180
        array $arguments,
181
    ): mixed {
182 787
        $result = $binding->alias === $alias
183
            ? $this->autowire(
184
                new Ctx(alias: $alias, class: $binding->alias, context: $context, singleton: $binding->singleton),
185 870
                $arguments,
186
            )
187
            //Binding is pointing to something else
188
            : $this->make($binding->alias, $arguments, $context);
189
190
        if ($binding->singleton && $arguments === []) {
191 870
            $this->state->singletons[$alias] = $result;
192 870
        }
193 1
194 1
        return $result;
195 1
    }
196 1
197 870
    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 482
    private function resolveAutowire(
213
        \Spiral\Core\Config\Autowire $binding,
214
        string $alias,
215
        Stringable|string|null $context,
216
        array $arguments,
217
    ): mixed {
218 482
        $instance = $binding->autowire->resolve($this, $arguments);
219
220 482
        $ctx = new Ctx(alias: $alias, class: $alias, context: $context, singleton: $binding->singleton);
221 14
        return $this->validateNewInstance($instance, $ctx, $arguments);
222 470
    }
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 478
        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 704
                : $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 704
            );
241
        }
242 704
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 704
                        \sprintf(
270
                            'Can\'t resolve `%s`: can\'t instantiate `%s` from WeakReference binding.',
271
                            $this->tracer->getRootAlias(),
272 915
                            $alias,
273
                        )
274 915
                    )
275
                );
276 915
            } 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 911
                }
302
            } catch (ContainerExceptionInterface $e) {
303
                $className = match (true) {
304 911
                    $e instanceof NotFoundException => NotFoundException::class,
305 911
                    default => ContainerException::class,
306 911
                };
307 911
                throw new $className($this->tracer->combineTraceMessage(\sprintf(
308
                    'Can\'t resolve `%s`.',
309 911
                    $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 914
            );
323
        } finally {
324 914
            $this->tracer->pop(false);
325 913
        }
326 913
    }
327 913
328
    /**
329
     * Automatically create class.
330 655
     * Object will be cached if the $arguments list is empty.
331 655
     *
332 655
     * @psalm-assert class-string $class
333 655
     *
334 655
     * @throws AutowireException
335
     * @throws \Throwable
336
     */
337
    private function autowire(Ctx $ctx, array $arguments): object
338 901
    {
339
        if (!(\class_exists($ctx->class) || (
340
            \interface_exists($ctx->class)
341 876
                &&
342 864
                (isset($this->state->injectors[$ctx->class]) || $this->binder->hasInjector($ctx->class))
343 876
        ))
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 481
        }
351
352
        // automatically create instance
353
        $instance = $this->createInstance($ctx, $arguments);
354
355
        // apply registration functions to created instance
356 481
        return $arguments === []
357 481
            ? $this->registerInstance($ctx, $instance)
358 481
            : $instance;
359
    }
360
361
    /**
362 481
     * @throws BadScopeException
363 475
     * @throws \Throwable
364 481
     */
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 905
    }
381
382
    /**
383
     * Create instance of desired class.
384 905
     *
385
     * @template TObject of object
386 905
     *
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 905
     * @throws ContainerException
393 905
     * @throws \Throwable
394 4
     */
395
    private function createInstance(
396
        Ctx $ctx,
397
        array $parameters,
398 904
    ): object {
399 462
        $class = $ctx->class;
400
        try {
401
            $ctx->reflection = $reflection = new \ReflectionClass($class);
402 896
        } 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 895
        if ($this->binder->hasInjector($class)) {
414
            return $this->resolveInjector($this->state->bindings[$ctx->class], $ctx, $parameters);
415 895
        }
416
417 819
        if (!$reflection->isInstantiable()) {
418 819
            $itIs = match (true) {
419 819
                $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 819
            try {
432 819
                $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 799
                throw new ContainerException(
437 799
                    $this->tracer->combineTraceMessage(
438 799
                        \sprintf(
439 1
                            'Can\'t resolve `%s`. %s',
440
                            $this->tracer->getRootAlias(),
441
                            $e->getMessage()
442 799
                        )
443 799
                    ),
444
                );
445
            } finally {
446
                $this->tracer->pop(true);
447 712
                $this->tracer->pop(false);
448
            }
449
            try {
450 874
                // 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 874
            } finally {
457
                $this->tracer->pop(true);
458 874
                $this->tracer->pop(false);
459
            }
460 874
        } else {
461
            // No constructor specified
462
            $instance = $reflection->newInstance();
463 874
        }
464 550
465
        return $instance;
466
    }
467
468 874
    /**
469 874
     * 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 874
        $ctx->reflection ??= new \ReflectionClass($instance);
474
475
        $instance = $this->runInflector($instance);
476
477
        //Declarative singletons
478
        if ($this->isSingleton($ctx)) {
479 874
            $this->state->singletons[$ctx->alias] = $instance;
480
        }
481 874
482 477
        // Register finalizer
483
        $finalizer = $this->getFinalizer($ctx, $instance);
484
        if ($finalizer !== null) {
485
            $this->state->finalizers[] = $finalizer;
486 865
        }
487 519
488
        return $instance;
489
    }
490 861
491
    /**
492
     * Check the class was configured as a singleton.
493 874
     */
494
    private function isSingleton(Ctx $ctx): bool
495
    {
496
        if ($ctx->singleton === true) {
497
            return true;
498
        }
499 874
500 874
        /** @psalm-suppress RedundantCondition https://github.com/vimeo/psalm/issues/9489 */
501 873
        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 874
        /**
511
         * @psalm-suppress UnnecessaryVarAnnotation
512 874
         * @var Finalize|null $attribute
513
         */
514 874
        $attribute = ($ctx->reflection->getAttributes(Finalize::class)[0] ?? null)?->newInstance();
515 874
        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 874
    private function runInflector(object $instance): object
526
    {
527
        $scope = $this->scope;
528 874
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