Passed
Pull Request — master (#941)
by Aleksei
09:16
created

Factory::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 11
ccs 9
cts 9
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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
             * @var InjectorInterface<TObject> $injectorInstance
147
             * @psalm-suppress RedundantCondition
148
             */
149 384
            $instance = $injectorInstance->createInjection($reflection, $ctx->parameter);
150 382
            if (!$reflection->isInstance($instance)) {
151 1
                throw new InjectionException(
152 1
                    \sprintf(
153 1
                        "Invalid injection response for '%s'.",
154 1
                        $reflection->getName()
155 1
                    )
156 1
                );
157
            }
158
159 381
            return $instance;
160
        } finally {
161 393
            $this->state->bindings[$reflection->getName()] ??= $binding; //new Injector($injector);
162
        }
163
    }
164
165 644
    private function resolveAlias(
166
        \Spiral\Core\Config\Alias $binding,
167
        string $alias,
168
        ?string $context,
169
        array $arguments,
170
    ): mixed {
171 644
        $result = $binding->alias === $alias
172 348
            ? $this->autowire(
173 348
                new Ctx(alias: $alias, class: $binding->alias, parameter: $context, singleton: $binding->singleton),
174 348
                $arguments,
175 348
            )
176 644
            //Binding is pointing to something else
177 636
            : $this->make($binding->alias, $arguments, $context);
178
179 641
        if ($binding->singleton && $arguments === []) {
180 398
            $this->state->singletons[$alias] = $result;
181
        }
182
183 641
        return $result;
184
    }
185
186 731
    private function resolveShared(
187
        \Spiral\Core\Config\Shared $binding,
188
        string $alias,
189
        ?string $context,
190
        array $arguments,
191
    ): object {
192 731
        $avoidCache = $arguments !== [];
193 731
        return $avoidCache
194 9
            ? $this->createInstance(
195 9
                new Ctx(alias: $alias, class: $binding->value::class, parameter: $context),
196 9
                $arguments,
197 9
            )
198 731
            : $binding->value;
199
    }
200
201 6
    private function resolveAutowire(
202
        \Spiral\Core\Config\Autowire $binding,
203
        string $alias,
204
        ?string $context,
205
        array $arguments,
206
    ): mixed {
207 6
        $instance = $binding->autowire->resolve($this, $arguments);
208
209 6
        $ctx = new Ctx(alias: $alias, class: $alias, parameter: $context, singleton: $binding->singleton);
210 6
        return $this->validateNewInstance($instance, $ctx, $arguments);
211
    }
212
213 388
    private function resolveFactory(
214
        \Spiral\Core\Config\Factory|DeferredFactory $binding,
215
        string $alias,
216
        ?string $context,
217
        array $arguments,
218
    ): mixed {
219 388
        $ctx = new Ctx(alias: $alias, class: $alias, parameter: $context, singleton: $binding->singleton);
220
        try {
221 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

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

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

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

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

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

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

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

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

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