Passed
Pull Request — master (#941)
by Aleksei
13:25 queued 04:54
created

Factory::make()   A

Complexity

Conditions 4
Paths 7

Size

Total Lines 40
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 4

Importance

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

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

84
                /** @scrutinizer ignore-type */ scope: $this->scope->getScopeName(),
Loading history...
85 862
                context: $context,
86 862
                binding: $binding,
87 862
            );
88 862
            $this->tracer->push(true);
89
90 862
            unset($this->state->bindings[$alias]);
91 862
            return match ($binding::class) {
92 862
                \Spiral\Core\Config\Alias::class => $this->resolveAlias($binding, $alias, $context, $parameters),
93 862
                \Spiral\Core\Config\Autowire::class => $this->resolveAutowire($binding, $alias, $context, $parameters),
94 862
                DeferredFactory::class,
95 862
                \Spiral\Core\Config\Factory::class => $this->resolveFactory($binding, $alias, $context, $parameters),
0 ignored issues
show
Bug introduced by
The type Spiral\Core\Config\Factory 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...
96 862
                \Spiral\Core\Config\Shared::class => $this->resolveShared($binding, $alias, $context, $parameters),
97 862
                Injectable::class => $this->resolveInjector($binding, $alias, $context, $parameters),
98 862
                \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...
99 862
                \Spiral\Core\Config\WeakReference::class => $this
100 862
                    ->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

100
                    ->resolveWeakReference(/** @scrutinizer ignore-type */ $binding, $alias, $context, $parameters),
Loading history...
101 862
                default => $binding,
102 862
            };
103
        } finally {
104 862
            $this->state->bindings[$alias] ??= $binding;
105 862
            $this->tracer->pop(true);
106 862
            $this->tracer->pop(false);
107
        }
108
    }
109
110 393
    private function resolveInjector(
111
        Injectable $binding,
112
        string $alias,
113
        ?string $context,
114
        array $arguments,
115
    ) {
116 393
        $ctx = new Ctx(alias: $alias, class: $alias, parameter: $context);
117
118
        // We have to construct class using external injector when we know exact context
119 393
        if ($arguments !== []) {
120
            // todo factory?
121
        }
122
123 393
        $class = $ctx->class;
124
        try {
125 393
            $ctx->reflection = $reflection = new \ReflectionClass($class);
126
        } catch (\ReflectionException $e) {
127
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
128
        }
129
130 393
        $injector = $binding->injector;
131
132
        try {
133 393
            $injectorInstance = \is_object($injector) ? $injector : $this->container->get($injector);
134
135 386
            if (!$injectorInstance instanceof InjectorInterface) {
136 2
                throw new InjectionException(
137 2
                    \sprintf(
138 2
                        "Class '%s' must be an instance of InjectorInterface for '%s'.",
139 2
                        $injectorInstance::class,
140 2
                        $reflection->getName()
141 2
                    )
142 2
                );
143
            }
144
145
            /**
146
             * @psalm-suppress RedundantCondition
147
             */
148 384
            $instance = $injectorInstance->createInjection($reflection, $ctx->parameter);
149 382
            if (!$reflection->isInstance($instance)) {
150 1
                throw new InjectionException(
151 1
                    \sprintf(
152 1
                        "Invalid injection response for '%s'.",
153 1
                        $reflection->getName()
154 1
                    )
155 1
                );
156
            }
157
158 381
            return $instance;
159
        } finally {
160 393
            $this->state->bindings[$reflection->getName()] ??= $binding; //new Injector($injector);
161
        }
162
    }
163
164 644
    private function resolveAlias(
165
        \Spiral\Core\Config\Alias $binding,
166
        string $alias,
167
        ?string $context,
168
        array $arguments,
169
    ): mixed {
170 644
        $result = $binding->alias === $alias
171 348
            ? $this->autowire(
172 348
                new Ctx(alias: $alias, class: $binding->alias, parameter: $context, singleton: $binding->singleton),
173 348
                $arguments,
174 348
            )
175 644
            //Binding is pointing to something else
176 636
            : $this->make($binding->alias, $arguments, $context);
177
178 641
        if ($binding->singleton && $arguments === []) {
179 398
            $this->state->singletons[$alias] = $result;
180
        }
181
182 641
        return $result;
183
    }
184
185 731
    private function resolveShared(
186
        \Spiral\Core\Config\Shared $binding,
187
        string $alias,
188
        ?string $context,
189
        array $arguments,
190
    ): object {
191 731
        $avoidCache = $arguments !== [];
192 731
        return $avoidCache
193 9
            ? $this->createInstance(
194 9
                new Ctx(alias: $alias, class: $binding->value::class, parameter: $context),
195 9
                $arguments,
196 9
            )
197 731
            : $binding->value;
198
    }
199
200 6
    private function resolveAutowire(
201
        \Spiral\Core\Config\Autowire $binding,
202
        string $alias,
203
        ?string $context,
204
        array $arguments,
205
    ): mixed {
206 6
        $instance = $binding->autowire->resolve($this, $arguments);
207
208 6
        $ctx = new Ctx(alias: $alias, class: $alias, parameter: $context, singleton: $binding->singleton);
209 6
        return $this->validateNewInstance($instance, $ctx, $arguments);
210
    }
211
212 388
    private function resolveFactory(
213
        \Spiral\Core\Config\Factory|DeferredFactory $binding,
214
        string $alias,
215
        ?string $context,
216
        array $arguments,
217
    ): mixed {
218 388
        $ctx = new Ctx(alias: $alias, class: $alias, parameter: $context, singleton: $binding->singleton);
219
        try {
220 388
            $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

220
            $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...
221 10
                ? ($binding->factory)()
222 379
                : $this->invoker->invoke($binding->factory, $arguments);
223 7
        } catch (NotCallableException $e) {
224
            throw new ContainerException(
225
                $this->tracer->combineTraceMessage(\sprintf('Invalid binding for `%s`.', $ctx->alias)),
226
                $e->getCode(),
227
                $e,
228
            );
229
        }
230
231 384
        return \is_object($instance) ? $this->validateNewInstance($instance, $ctx, $arguments) : $instance;
232
    }
233
234 590
    private function resolveWeakReference(
235
        \Spiral\Core\Config\WeakReference $binding,
236
        string $alias,
237
        ?string $context,
238
        array $arguments,
239
    ): ?object {
240 590
        $avoidCache = $arguments !== [];
241
242 590
        if (($avoidCache || $binding->reference->get() === null) && \class_exists($alias)) {
243
            try {
244 3
                $this->tracer->push(false, alias: $alias, source: WeakReference::class, context: $context);
0 ignored issues
show
Bug introduced by
$context 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

244
                $this->tracer->push(false, alias: $alias, source: WeakReference::class, /** @scrutinizer ignore-type */ context: $context);
Loading history...
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

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

301
        $this->tracer->push(false, /** @scrutinizer ignore-type */ action: 'autowire', alias: $alias, context: $context);
Loading history...
Bug introduced by
$context 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

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

405
                $reflection->/** @scrutinizer ignore-call */ 
406
                             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...
406 4
                $reflection->isAbstract() => 'Abstract class',
407 4
                default => 'Class',
408 4
            };
409 4
            throw new ContainerException(
410 4
                $this->tracer->combineTraceMessage(\sprintf('%s `%s` can not be constructed.', $itIs, $class)),
411 4
            );
412
        }
413
414 740
        $constructor = $reflection->getConstructor();
415
416 740
        if ($constructor !== null) {
417
            try {
418 660
                $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

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

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

487
        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...
488 422
            return true;
489
        }
490
491 708
        return $ctx->reflection->getAttributes(Singleton::class) !== [];
492
    }
493
494 720
    private function getFinalizer(Ctx $ctx, object $instance): ?callable
495
    {
496
        /**
497
         * @psalm-suppress UnnecessaryVarAnnotation
498
         * @var Finalize|null $attribute
499
         */
500 720
        $attribute = ($ctx->reflection->getAttributes(Finalize::class)[0] ?? null)?->newInstance();
501 720
        if ($attribute === null) {
502 719
            return null;
503
        }
504
505 4
        return [$instance, $attribute->method];
506
    }
507
508
    /**
509
     * Find and run inflector
510
     */
511 720
    private function runInflector(object $instance): object
512
    {
513 720
        $scope = $this->scope;
514
515 720
        while ($scope !== null) {
516 720
            foreach ($this->state->inflectors as $class => $inflectors) {
517 5
                if ($instance instanceof $class) {
518 5
                    foreach ($inflectors as $inflector) {
519 5
                        $instance = $inflector->getParametersCount() > 1
520 1
                            ? $this->invoker->invoke($inflector->inflector, [$instance])
521 4
                            : ($inflector->inflector)($instance);
522
                    }
523
                }
524
            }
525
526 720
            $scope = $scope->getParentScope();
527
        }
528
529 720
        return $instance;
530
    }
531
}
532