Passed
Pull Request — master (#1045)
by Aleksei
09:49
created

Factory::validateArguments()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.2559

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 9
ccs 3
cts 5
cp 0.6
rs 10
cc 2
nc 2
nop 2
crap 2.2559
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
    private Scope $scope;
50
51 1310
    public function __construct(Registry $constructor)
52
    {
53 1310
        $constructor->set('factory', $this);
54
55 1310
        $this->state = $constructor->get('state', State::class);
56 1310
        $this->binder = $constructor->get('binder', BinderInterface::class);
57 1310
        $this->invoker = $constructor->get('invoker', InvokerInterface::class);
58 1310
        $this->container = $constructor->get('container', ContainerInterface::class);
59 1310
        $this->resolver = $constructor->get('resolver', ResolverInterface::class);
60 1310
        $this->tracer = $constructor->get('tracer', Tracer::class);
61 1310
        $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
     * @throws \Throwable
68
     */
69 1121
    public function make(string $alias, array $parameters = [], Stringable|string|null $context = null): mixed
70
    {
71 1121
        if ($parameters === [] && \array_key_exists($alias, $this->state->singletons)) {
72 513
            return $this->state->singletons[$alias];
73
        }
74
75 1121
        $binding = $this->state->bindings[$alias] ?? null;
76
77 1121
        if ($binding === null) {
78 930
            return $this->resolveWithoutBinding($alias, $parameters, $context);
79
        }
80
81
        try {
82 1046
            $this->tracer->push(
83 1046
                false,
84 1046
                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 1046
                alias: $alias,
86 1046
                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 1046
                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 1046
                binding: $binding,
89 1046
            );
90 1046
            $this->tracer->push(true);
91
92 1046
            unset($this->state->bindings[$alias]);
93 1046
            return match ($binding::class) {
94 1046
                \Spiral\Core\Config\Alias::class => $this->resolveAlias($binding, $alias, $context, $parameters),
95 1046
                \Spiral\Core\Config\Autowire::class => $this->resolveAutowire($binding, $alias, $context, $parameters),
96 1046
                DeferredFactory::class,
97 1046
                \Spiral\Core\Config\Factory::class => $this->resolveFactory($binding, $alias, $context, $parameters),
98 1046
                \Spiral\Core\Config\Shared::class => $this->resolveShared($binding, $alias, $context, $parameters),
99 1046
                Injectable::class => $this->resolveInjector(
100 1046
                    $binding,
101 1046
                    new Ctx(alias: $alias, class: $alias, context: $context),
102 1046
                    $parameters,
103 1046
                ),
104 1046
                \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 1046
                \Spiral\Core\Config\WeakReference::class => $this
106 1046
                    ->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 1046
                default => $binding,
108 1046
            };
109
        } finally {
110 1046
            $this->state->bindings[$alias] ??= $binding;
111 1046
            $this->tracer->pop(true);
112 1046
            $this->tracer->pop(false);
113
        }
114
    }
115
116
    /**
117
     * @psalm-suppress UnusedParam
118
     * todo wat should we do with $arguments?
119
     */
120 476
    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 476
        $context = $ctx->context;
123
        try {
124 476
            $reflection = $ctx->reflection ??= new \ReflectionClass($ctx->class);
125
        } catch (\ReflectionException $e) {
126
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
127
        }
128
129 476
        $injector = $binding->injector;
130
131
        try {
132 476
            $injectorInstance = \is_object($injector) ? $injector : $this->container->get($injector);
133
134 469
            if (!$injectorInstance instanceof InjectorInterface) {
135 2
                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
            }
143
144
            /** @var array<class-string<InjectorInterface>, \ReflectionMethod|false> $cache reflection for extended injectors */
145 467
            static $cache = [];
146 467
            $extended = $cache[$injectorInstance::class] ??= (
147 467
                static fn (\ReflectionType $type): bool =>
148 18
                $type::class === \ReflectionUnionType::class || (string)$type === 'mixed'
149 467
            )(
150 467
                ($refMethod = new \ReflectionMethod($injectorInstance, 'createInjection'))
151 467
                    ->getParameters()[1]->getType()
152 467
            ) ? $refMethod : false;
153
154 467
            $asIs = $extended && (\is_string($context) || $this->validateArguments($extended, [$reflection, $context]));
155 467
            $instance = $injectorInstance->createInjection($reflection, match (true) {
156 467
                $asIs => $context,
157 467
                $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 467
                default => (string)$context,
159 467
            });
160
161 465
            if (!$reflection->isInstance($instance)) {
162 1
                throw new InjectionException(
163 1
                    \sprintf(
164 1
                        "Invalid injection response for '%s'.",
165 1
                        $reflection->getName()
166 1
                    )
167 1
                );
168
            }
169
170 464
            return $instance;
171
        } finally {
172 476
            $this->state->bindings[$ctx->class] ??= $binding;
173
        }
174
    }
175
176 803
    private function resolveAlias(
177
        \Spiral\Core\Config\Alias $binding,
178
        string $alias,
179
        Stringable|string|null $context,
180
        array $arguments,
181
    ): mixed {
182 803
        $result = $binding->alias === $alias
183 411
            ? $this->autowire(
184 411
                new Ctx(alias: $alias, class: $binding->alias, context: $context, singleton: $binding->singleton),
185 411
                $arguments,
186 411
            )
187 803
            //Binding is pointing to something else
188 795
            : $this->make($binding->alias, $arguments, $context);
189
190 800
        if ($binding->singleton && $arguments === []) {
191 529
            $this->state->singletons[$alias] = $result;
192
        }
193
194 800
        return $result;
195
    }
196
197 870
    private function resolveShared(
198
        \Spiral\Core\Config\Shared $binding,
199
        string $alias,
200
        Stringable|string|null $context,
201
        array $arguments,
202
    ): object {
203 870
        $avoidCache = $arguments !== [];
204 870
        return $avoidCache
205 1
            ? $this->createInstance(
206 1
                new Ctx(alias: $alias, class: $binding->value::class, context: $context),
207 1
                $arguments,
208 1
            )
209 870
            : $binding->value;
210
    }
211
212 7
    private function resolveAutowire(
213
        \Spiral\Core\Config\Autowire $binding,
214
        string $alias,
215
        Stringable|string|null $context,
216
        array $arguments,
217
    ): mixed {
218 7
        $instance = $binding->autowire->resolve($this, $arguments);
219
220 7
        $ctx = new Ctx(alias: $alias, class: $alias, context: $context, singleton: $binding->singleton);
221 7
        return $this->validateNewInstance($instance, $ctx, $arguments);
222
    }
223
224 484
    private function resolveFactory(
225
        \Spiral\Core\Config\Factory|DeferredFactory $binding,
226
        string $alias,
227
        Stringable|string|null $context,
228
        array $arguments,
229
    ): mixed {
230 484
        $ctx = new Ctx(alias: $alias, class: $alias, context: $context, singleton: $binding->singleton);
231
        try {
232 484
            $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 16
                ? ($binding->factory)()
234 470
                : $this->invoker->invoke($binding->factory, $arguments);
235 8
        } catch (NotCallableException $e) {
236
            throw new ContainerException(
237
                $this->tracer->combineTraceMessage(\sprintf('Invalid binding for `%s`.', $ctx->alias)),
238
                $e->getCode(),
239
                $e,
240
            );
241
        }
242
243 480
        return \is_object($instance) ? $this->validateNewInstance($instance, $ctx, $arguments) : $instance;
244
    }
245
246 708
    private function resolveWeakReference(
247
        \Spiral\Core\Config\WeakReference $binding,
248
        string $alias,
249
        Stringable|string|null $context,
250
        array $arguments,
251
    ): ?object {
252 708
        $avoidCache = $arguments !== [];
253
254 708
        if (($avoidCache || $binding->reference->get() === null) && \class_exists($alias)) {
255
            try {
256 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

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 3
                $object = $this->createInstance(
259 3
                    new Ctx(alias: $alias, class: $alias, context: $context),
260 3
                    $arguments,
261 3
                );
262 3
                if ($avoidCache) {
263 1
                    return $object;
264
                }
265 2
                $binding->reference = WeakReference::create($object);
266
            } catch (\Throwable) {
267
                throw new ContainerException(
268
                    $this->tracer->combineTraceMessage(
269
                        \sprintf(
270
                            'Can\'t resolve `%s`: can\'t instantiate `%s` from WeakReference binding.',
271
                            $this->tracer->getRootAlias(),
272
                            $alias,
273
                        )
274
                    )
275
                );
276
            } finally {
277 3
                $this->tracer->pop();
278
            }
279
        }
280
281 708
        return $binding->reference->get();
282
    }
283
284 930
    private function resolveWithoutBinding(
285
        string $alias,
286
        array $parameters = [],
287
        Stringable|string|null $context = null
288
    ): mixed {
289 930
        $parent = $this->scope->getParent();
290
291 930
        if ($parent !== null) {
292
            try {
293 19
                $this->tracer->push(false, ...[
294 19
                    'current scope' => $this->scope->getScopeName(),
295 19
                    'jump to parent scope' => $this->scope->getParentScope()->getScopeName(),
296 19
                ]);
297 19
                return $parent->make($alias, $parameters, $context);
298 3
            } catch (BadScopeException $e) {
299 3
                if ($this->scope->getScopeName() !== $e->getScope()) {
300 3
                    throw $e;
301
                }
302 1
            } catch (ContainerExceptionInterface $e) {
303 1
                $className = match (true) {
304 1
                    $e instanceof NotFoundException => NotFoundException::class,
305 1
                    default => ContainerException::class,
306 1
                };
307 1
                throw new $className($this->tracer->combineTraceMessage(\sprintf(
308 1
                    'Can\'t resolve `%s`.',
309 1
                    $alias,
310 1
                )), previous: $e);
311
            } finally {
312 19
                $this->tracer->pop(false);
313
            }
314
        }
315
316 926
        $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 926
            return $this->autowire(
320 926
                new Ctx(alias: $alias, class: $alias, context: $context),
321 926
                $parameters,
322 926
            );
323
        } finally {
324 926
            $this->tracer->pop(false);
325
        }
326
    }
327
328
    /**
329
     * Automatically create class.
330
     * Object will be cached if the $arguments list is empty.
331
     *
332
     * @psalm-assert class-string $class
333
     *
334
     * @throws AutowireException
335
     * @throws \Throwable
336
     */
337 929
    private function autowire(Ctx $ctx, array $arguments): object
338
    {
339 929
        if (!(\class_exists($ctx->class) || (
340 928
            \interface_exists($ctx->class)
341 928
                &&
342 928
                (isset($this->state->injectors[$ctx->class]) || $this->binder->hasInjector($ctx->class))
343
        ))
344
        ) {
345 655
            throw new NotFoundException($this->tracer->combineTraceMessage(\sprintf(
346 655
                'Can\'t resolve `%s`: undefined class or binding `%s`.',
347 655
                $this->tracer->getRootAlias(),
348 655
                $ctx->class,
349 655
            )));
350
        }
351
352
        // automatically create instance
353 916
        $instance = $this->createInstance($ctx, $arguments);
354
355
        // apply registration functions to created instance
356 891
        return $arguments === []
357 879
            ? $this->registerInstance($ctx, $instance)
358 891
            : $instance;
359
    }
360
361
    /**
362
     * @throws BadScopeException
363
     * @throws \Throwable
364
     */
365 481
    private function validateNewInstance(
366
        object $instance,
367
        Ctx $ctx,
368
        array $arguments,
369
    ): object {
370
        // Check scope name
371 481
        $ctx->reflection = new \ReflectionClass($instance);
372 481
        $scopeName = ($ctx->reflection->getAttributes(ScopeAttribute::class)[0] ?? null)?->newInstance()->name;
373 481
        if ($scopeName !== null && $scopeName !== $this->scope->getScopeName()) {
374
            throw new BadScopeException($scopeName, $instance::class);
375
        }
376
377 481
        return $arguments === []
378 475
            ? $this->registerInstance($ctx, $instance)
379 481
            : $instance;
380
    }
381
382
    /**
383
     * Create instance of desired class.
384
     *
385
     * @template TObject of object
386
     *
387
     * @param Ctx<TObject> $ctx
388
     * @param array $arguments Constructor arguments.
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
     * @throws ContainerException
393
     * @throws \Throwable
394
     */
395 920
    private function createInstance(
396
        Ctx $ctx,
397
        array $arguments,
398
    ): object {
399 920
        $class = $ctx->class;
400
        try {
401 920
            $ctx->reflection = $reflection = new \ReflectionClass($class);
402
        } catch (\ReflectionException $e) {
403
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
404
        }
405
406
        // Check scope name
407 920
        $scope = ($reflection->getAttributes(ScopeAttribute::class)[0] ?? null)?->newInstance()->name;
408 920
        if ($scope !== null && $scope !== $this->scope->getScopeName()) {
409 4
            throw new BadScopeException($scope, $class);
410
        }
411
412
        // We have to construct class using external injector when we know the exact context
413 919
        if ($arguments === [] && $this->binder->hasInjector($class)) {
414 462
            return $this->resolveInjector($this->state->bindings[$ctx->class], $ctx, $arguments);
415
        }
416
417 911
        if (!$reflection->isInstantiable()) {
418 4
            $itIs = match (true) {
419 4
                $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 4
                $reflection->isAbstract() => 'Abstract class',
421 4
                default => 'Class',
422 4
            };
423 4
            throw new ContainerException(
424 4
                $this->tracer->combineTraceMessage(\sprintf('%s `%s` can not be constructed.', $itIs, $class)),
425 4
            );
426
        }
427
428 910
        $constructor = $reflection->getConstructor();
429
430 910
        if ($constructor !== null) {
431
            try {
432 823
                $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 823
                $this->tracer->push(true);
434 823
                $args = $this->resolver->resolveArguments($constructor, $arguments);
435 21
            } catch (ValidationException $e) {
436 6
                throw new ContainerException(
437 6
                    $this->tracer->combineTraceMessage(
438 6
                        \sprintf(
439 6
                            'Can\'t resolve `%s`. %s',
440 6
                            $this->tracer->getRootAlias(),
441 6
                            $e->getMessage()
442 6
                        )
443 6
                    ),
444 6
                );
445
            } finally {
446 823
                $this->tracer->pop(true);
447 823
                $this->tracer->pop(false);
448
            }
449
            try {
450
                // Using constructor with resolved arguments
451 803
                $this->tracer->push(false, call: "$class::__construct", arguments: $args);
452 803
                $this->tracer->push(true);
453 803
                $instance = new $class(...$args);
454 1
            } catch (\TypeError $e) {
455
                throw new WrongTypeException($constructor, $e);
456
            } finally {
457 803
                $this->tracer->pop(true);
458 803
                $this->tracer->pop(false);
459
            }
460
        } else {
461
            // No constructor specified
462 726
            $instance = $reflection->newInstance();
463
        }
464
465 889
        return $instance;
466
    }
467
468
    /**
469
     * Register instance in container, might perform methods like auto-singletons, log populations, etc.
470
     */
471 889
    private function registerInstance(Ctx $ctx, object $instance): object
472
    {
473 889
        $ctx->reflection ??= new \ReflectionClass($instance);
474
475 889
        $instance = $this->runInflector($instance);
476
477
        //Declarative singletons
478 889
        if ($this->isSingleton($ctx)) {
479 551
            $this->state->singletons[$ctx->alias] = $instance;
480
        }
481
482
        // Register finalizer
483 889
        $finalizer = $this->getFinalizer($ctx, $instance);
484 889
        if ($finalizer !== null) {
485 4
            $this->state->finalizers[] = $finalizer;
486
        }
487
488 889
        return $instance;
489
    }
490
491
    /**
492
     * Check the class was configured as a singleton.
493
     */
494 889
    private function isSingleton(Ctx $ctx): bool
495
    {
496 889
        if ($ctx->singleton === true) {
497 478
            return true;
498
        }
499
500
        /** @psalm-suppress RedundantCondition https://github.com/vimeo/psalm/issues/9489 */
501 880
        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 519
            return true;
503
        }
504
505 876
        return $ctx->reflection->getAttributes(Singleton::class) !== [];
506
    }
507
508 889
    private function getFinalizer(Ctx $ctx, object $instance): ?callable
509
    {
510
        /**
511
         * @psalm-suppress UnnecessaryVarAnnotation
512
         * @var Finalize|null $attribute
513
         */
514 889
        $attribute = ($ctx->reflection->getAttributes(Finalize::class)[0] ?? null)?->newInstance();
515 889
        if ($attribute === null) {
516 888
            return null;
517
        }
518
519 4
        return [$instance, $attribute->method];
520
    }
521
522
    /**
523
     * Find and run inflector
524
     */
525 889
    private function runInflector(object $instance): object
526
    {
527 889
        $scope = $this->scope;
528
529 889
        while ($scope !== null) {
530 889
            foreach ($this->state->inflectors as $class => $inflectors) {
531 5
                if ($instance instanceof $class) {
532 5
                    foreach ($inflectors as $inflector) {
533 5
                        $instance = $inflector->getParametersCount() > 1
534 1
                            ? $this->invoker->invoke($inflector->inflector, [$instance])
535 4
                            : ($inflector->inflector)($instance);
536
                    }
537
                }
538
            }
539
540 889
            $scope = $scope->getParentScope();
541
        }
542
543 889
        return $instance;
544
    }
545
546 5
    private function validateArguments(ContextFunction $reflection, array $arguments = []): bool
547
    {
548
        try {
549 5
            $this->resolver->validateArguments($reflection, $arguments);
550
        } catch (\Throwable) {
551
            return false;
552
        }
553
554 5
        return true;
555
    }
556
}
557