Test Failed
Pull Request — master (#1041)
by Aleksei
06:51
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
    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

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

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

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

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

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

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

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

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

416
                $reflection->/** @scrutinizer ignore-call */ 
417
                             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...
417 817
                $reflection->isAbstract() => 'Abstract class',
418 817
                default => 'Class',
419 817
            };
420 21
            throw new ContainerException(
421 6
                $this->tracer->combineTraceMessage(\sprintf('%s `%s` can not be constructed.', $itIs, $class)),
422 6
            );
423 6
        }
424 6
425 6
        $constructor = $reflection->getConstructor();
426 6
427 6
        if ($constructor !== null) {
428 6
            try {
429 6
                $this->tracer->push(false, action: 'resolve arguments', signature: $constructor);
0 ignored issues
show
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

429
                $this->tracer->push(false, /** @scrutinizer ignore-type */ action: 'resolve arguments', signature: $constructor);
Loading history...
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

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

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