Test Failed
Pull Request — master (#941)
by Aleksei
09:19
created

Factory::createInstance()   B

Complexity

Conditions 10
Paths 54

Size

Total Lines 71
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 10.0056

Importance

Changes 4
Bugs 2 Features 0
Metric Value
eloc 45
c 4
b 2
f 0
dl 0
loc 71
rs 7.3333
ccs 25
cts 26
cp 0.9615
cc 10
nc 54
nop 2
crap 10.0056

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 1111
    private Scope $scope;
48
49 1111
    public function __construct(Registry $constructor)
50
    {
51 1111
        $constructor->set('factory', $this);
52 1111
53 1111
        $this->state = $constructor->get('state', State::class);
54 1111
        $this->binder = $constructor->get('binder', BinderInterface::class);
55 1111
        $this->invoker = $constructor->get('invoker', InvokerInterface::class);
56 1111
        $this->container = $constructor->get('container', ContainerInterface::class);
57 1111
        $this->resolver = $constructor->get('resolver', ResolverInterface::class);
58
        $this->tracer = $constructor->get('tracer', Tracer::class);
59
        $this->scope = $constructor->get('scope', Scope::class);
60
    }
61
62
    /**
63
     * @param string|null $context Related to parameter caused injection if any.
64
     *
65 934
     * @throws \Throwable
66
     */
67 934
    public function make(string $alias, array $parameters = [], string $context = null): mixed
68 752
    {
69
        if ($parameters === [] && \array_key_exists($alias, $this->state->singletons)) {
70
            return $this->state->singletons[$alias];
71 855
        }
72 855
73
        $binding = $this->state->bindings[$alias] ?? null;
74 855
75 855
        if ($binding === null) {
76 855
            return $this->resolveWithoutBinding($alias, $parameters, $context);
77 855
        }
78 855
79 855
        try {
80 855
            $this->tracer->push(
81 855
                false,
82 855
                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
                alias: $alias,
84 855
                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 820
                context: $context,
86 585
                binding: $binding,
87
            );
88
            $this->tracer->push(true);
89
90 737
            unset($this->state->bindings[$alias]);
91 11
            return match ($binding::class) {
92 11
                \Spiral\Core\Config\Alias::class => $this->resolveAlias($binding, $alias, $context, $parameters),
93 11
                \Spiral\Core\Config\Autowire::class => $this->resolveAutowire($binding, $alias, $context, $parameters),
94 11
                DeferredFactory::class,
95 737
                \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
                \Spiral\Core\Config\Shared::class => $this->resolveShared($binding, $alias, $context, $parameters),
97
                Injectable::class => $this->resolveInjector($binding, $alias, $context, $parameters),
98 664
                \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 664
                \Spiral\Core\Config\WeakReference::class => $this
100 617
                    ->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 617
                default => $binding,
102 3
            };
103 617
        } finally {
104 617
            $this->state->bindings[$alias] ??= $binding;
105
            $this->tracer->pop(true);
106
            $this->tracer->pop(false);
107 433
        }
108 416
    }
109
110 433
    private function resolveInjector(
111
        Injectable $binding,
112 433
        string $alias,
113 343
        ?string $context,
114 433
        array $arguments
115
    ) {
116 433
        $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 855
        if ($arguments !== []) {
120 855
            // todo factory?
121
        }
122
123
        $class = $ctx->class;
124 585
        try {
125
            $ctx->reflection = $reflection = new \ReflectionClass($class);
126
        } catch (\ReflectionException $e) {
127
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
128
        }
129
130 585
        $injector = $binding->injector;
131
132 585
        try {
133
            $injectorInstance = \is_object($injector) ? $injector : $this->container->get($injector);
134 3
135
            if (!$injectorInstance instanceof InjectorInterface) {
136 3
                throw new InjectionException(
137 3
                    \sprintf(
138 3
                        "Class '%s' must be an instance of InjectorInterface for '%s'.",
139 3
                        $injectorInstance::class,
140 3
                        $reflection->getName()
141 1
                    )
142
                );
143 2
            }
144
145
            /**
146
             * @var InjectorInterface<TObject> $injectorInstance
147
             * @psalm-suppress RedundantCondition
148
             */
149
            $instance = $injectorInstance->createInjection($reflection, $ctx->parameter);
150
            if (!$reflection->isInstance($instance)) {
151
                throw new InjectionException(
152
                    \sprintf(
153
                        "Invalid injection response for '%s'.",
154
                        $reflection->getName()
155 3
                    )
156
                );
157
            }
158
159 585
            return $instance;
160
        } finally {
161
            $this->state->bindings[$reflection->getName()] ??= $binding; //new Injector($injector);
162 752
        }
163
    }
164 752
165
    private function resolveAlias(
166 752
        \Spiral\Core\Config\Alias $binding,
167
        string $alias,
168 13
        ?string $context,
169 13
        array $arguments,
170 13
    ): mixed {
171 13
        $result = $binding->alias === $alias
172 13
            ? $this->autowire(
173 3
                new Ctx(alias: $alias, class: $binding->alias, parameter: $context, singleton: $binding->singleton),
174 3
                $arguments,
175 3
            )
176
            //Binding is pointing to something else
177 1
            : $this->make($binding->alias, $arguments, $context);
178 1
179 1
        if ($binding->singleton && $arguments === []) {
180 1
            $this->state->singletons[$alias] = $result;
181 1
        }
182 1
183 1
        return $result;
184 1
    }
185 1
186
    private function resolveShared(
187 13
        \Spiral\Core\Config\Shared $binding,
188
        string $alias,
189
        ?string $context,
190
        array $arguments,
191 749
    ): object {
192
        $avoidCache = $arguments !== [];
193
        return $avoidCache
194 749
            ? $this->createInstance(
195 749
                new Ctx(alias: $alias, class: $binding->value::class, parameter: $context),
196 749
                $arguments,
197 749
            )
198
            : $binding->value;
199 749
    }
200
201
    private function resolveAutowire(
202
        \Spiral\Core\Config\Autowire $binding,
203
        string $alias,
204
        ?string $context,
205
        array $arguments,
206
    ): mixed {
207
        $instance = $binding->autowire->resolve($this, $arguments);
208
209
        $ctx = new Ctx(alias: $alias, class: $alias, parameter: $context, singleton: $binding->singleton);
210
        return $this->validateNewInstance($instance, $ctx, $arguments);
211
    }
212 752
213
    private function resolveFactory(
214
        \Spiral\Core\Config\Factory|DeferredFactory $binding,
215 752
        string $alias,
216 751
        ?string $context,
217 751
        array $arguments,
218 751
    ): mixed {
219
        $ctx = new Ctx(alias: $alias, class: $alias, parameter: $context, singleton: $binding->singleton);
220
        try {
221 496
            $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 496
                ? ($binding->factory)()
223 496
                : $this->invoker->invoke($binding->factory, $arguments);
224 496
        } catch (NotCallableException $e) {
225 496
            throw new ContainerException(
226
                $this->tracer->combineTraceMessage(\sprintf('Invalid binding for `%s`.', $ctx->alias)),
227
                $e->getCode(),
228
                $e,
229 740
            );
230
        }
231
232 712
        return \is_object($instance) ? $this->validateNewInstance($instance, $ctx, $arguments) : $instance;
233 703
    }
234 712
235
    private function resolveWeakReference(
236
        \Spiral\Core\Config\WeakReference $binding,
237
        string $alias,
238
        ?string $context,
239
        array $arguments,
240
    ): ?object {
241
        $avoidCache = $arguments !== [];
242
243 426
        if (($avoidCache || $binding->reference->get() === null) && \class_exists($alias)) {
244
            try {
245
                $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
                $object = $this->createInstance(
248 426
                    new Ctx(alias: $alias, class: $alias, parameter: $context),
249
                    $arguments,
250 395
                );
251
                if ($avoidCache) {
252 389
                    return $object;
253 4
                }
254
                $binding->reference = WeakReference::create($object);
255
            } catch (\Throwable) {
256 386
                throw new ContainerException(
257 8
                    $this->tracer->combineTraceMessage(
258 2
                        \sprintf(
259 2
                            'Can\'t resolve `%s`: can\'t instantiate `%s` from WeakReference binding.',
260 2
                            $this->tracer->getRootAlias(),
261 2
                            $alias,
262 2
                        )
263
                    )
264
                );
265
            } finally {
266
                $this->tracer->pop();
267 384
            }
268 381
        }
269 381
270 381
        return $binding->reference->get();
271
    }
272
273
    private function resolveWithoutBinding(string $alias, array $parameters = [], string $context = null): mixed
274
    {
275
        $parent = $this->scope->getParent();
276 421
277 407
        if ($parent !== null) {
278 421
            try {
279
                $this->tracer->push(false, ...[
280
                    'current scope' => $this->scope->getScopeName(),
281
                    'jump to parent scope' => $this->scope->getParentScope()->getScopeName(),
282
                ]);
283
                return $parent->make($alias, $parameters, $context);
284
            } catch (BadScopeException $e) {
285
                if ($this->scope->getScopeName() !== $e->getScope()) {
286
                    throw $e;
287
                }
288
            } catch (ContainerExceptionInterface $e) {
289
                $className = match (true) {
290
                    $e instanceof NotFoundException => NotFoundException::class,
291
                    default => ContainerException::class,
292
                };
293
                throw new $className($this->tracer->combineTraceMessage(\sprintf(
294 744
                    'Can\'t resolve `%s`.',
295
                    $alias,
296
                )), previous: $e);
297
            } finally {
298 744
                $this->tracer->pop(false);
299
            }
300 744
        }
301
302
        $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
            return $this->autowire(
306 744
                new Ctx(alias: $alias, class: $alias, parameter: $context),
307 744
                $parameters,
308 4
            );
309
        } finally {
310
            $this->tracer->pop(false);
311
        }
312 743
    }
313 390
314
    /**
315
     * Automatically create class.
316 390
     * Object will be cached if the $arguments list is empty.
317
     *
318 382
     * @psalm-assert class-string $class
319 2
     *
320 2
     * @throws AutowireException
321 2
     * @throws \Throwable
322 2
     */
323 2
    private function autowire(Ctx $ctx, array $arguments): object
324 2
    {
325 2
        /** @psalm-suppress NoValue, InvalidArrayOffset */
326
        if (!(\class_exists($ctx->class) || (
327
            \interface_exists($ctx->class)
328
                &&
329
                (isset($this->state->injectors[$ctx->class]) || $this->binder->hasInjector($ctx->class))
330
        ))
331
        ) {
332 380
            throw new NotFoundException($this->tracer->combineTraceMessage(\sprintf(
333 378
                'Can\'t resolve `%s`: undefined class or binding `%s`.',
334 1
                $this->tracer->getRootAlias(),
335 1
                $ctx->class,
336 1
            )));
337 1
        }
338 1
339 1
        // automatically create instance
340
        $instance = $this->createInstance($ctx, $arguments);
341
342 377
        // apply registration functions to created instance
343
        return $arguments === []
344 390
            ? $this->registerInstance($ctx, $instance)
345
            : $instance;
346
    }
347
348 731
    /**
349 4
     * @throws BadScopeException
350 4
     * @throws \Throwable
351 4
     */
352 4
    private function validateNewInstance(
353 4
        object $instance,
354 4
        Ctx $ctx,
355 4
        array $arguments,
356 4
    ): object {
357
        // Check scope name
358
        $ctx->reflection = new \ReflectionClass($instance);
359 730
        $scopeName = ($ctx->reflection->getAttributes(ScopeAttribute::class)[0] ?? null)?->newInstance()->name;
360
        if ($scopeName !== null && $scopeName !== $this->scope->getScopeName()) {
361 730
            throw new BadScopeException($scopeName, $instance::class);
362
        }
363 655
364 655
        return $arguments === []
365 655
            ? $this->registerInstance($ctx, $instance)
366 20
            : $instance;
367 6
    }
368 6
369 6
    /**
370 6
     * Create instance of desired class.
371 6
     *
372 6
     * @template TObject of object
373 6
     *
374 6
     * @param Ctx<TObject> $ctx
375 6
     * @param array $parameters Constructor parameters.
376
     *
377 655
     * @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 655
     *
379
     * @throws ContainerException
380
     * @throws \Throwable
381
     */
382 635
    private function createInstance(
383 635
        Ctx $ctx,
384 635
        array $parameters,
385 1
    ): object {
386
        $class = $ctx->class;
387
        try {
388 635
            $ctx->reflection = $reflection = new \ReflectionClass($class);
389 635
        } catch (\ReflectionException $e) {
390
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
391
        }
392
393 587
        // Check scope name
394
        $scope = ($reflection->getAttributes(ScopeAttribute::class)[0] ?? null)?->newInstance()->name;
395
        if ($scope !== null && $scope !== $this->scope->getScopeName()) {
396 709
            throw new BadScopeException($scope, $class);
397
        }
398
399
        //We have to construct class using external injector when we know exact context
400
        if ($parameters === [] && $this->binder->hasInjector($class)) {
401
            return $this->resolveInjector($this->state->bindings[$ctx->class], $ctx->class, $ctx->parameter, $parameters);
402
        }
403
404
        if (!$reflection->isInstantiable()) {
405
            $itIs = match (true) {
406
                $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
                $reflection->isAbstract() => 'Abstract class',
408
                default => 'Class',
409
            };
410 711
            throw new ContainerException(
411
                $this->tracer->combineTraceMessage(\sprintf('%s `%s` can not be constructed.', $itIs, $class)),
412 711
            );
413
        }
414
415 711
        $constructor = $reflection->getConstructor();
416 450
417
        if ($constructor !== null) {
418
            try {
419
                $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 711
                $this->tracer->push(true);
421 711
                $arguments = $this->resolver->resolveArguments($constructor, $parameters);
422 4
            } catch (ValidationException $e) {
423
                throw new ContainerException(
424
                    $this->tracer->combineTraceMessage(
425 711
                        \sprintf(
426
                            'Can\'t resolve `%s`. %s',
427
                            $this->tracer->getRootAlias(),
428
                            $e->getMessage()
429
                        )
430
                    ),
431 711
                );
432
            } finally {
433 711
                $this->tracer->pop(true);
434 409
                $this->tracer->pop(false);
435
            }
436
            try {
437
                // Using constructor with resolved arguments
438 704
                $this->tracer->push(false, call: "$class::__construct", arguments: $arguments);
439 419
                $this->tracer->push(true);
440
                $instance = new $class(...$arguments);
441
            } catch (\TypeError $e) {
442 701
                throw new WrongTypeException($constructor, $e);
443
            } finally {
444
                $this->tracer->pop(true);
445 711
                $this->tracer->pop(false);
446
            }
447
        } else {
448
            // No constructor specified
449
            $instance = $reflection->newInstance();
450
        }
451 711
452 711
        return $instance;
453 710
    }
454
455
    /**
456 4
     * 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
    private function registerInstance(Ctx $ctx, object $instance): object
467
    {
468
        $ctx->reflection ??= new \ReflectionClass($instance);
469
470
        $instance = $this->runInflector($instance);
471
472
        //Declarative singletons
473
        if ($this->isSingleton($ctx)) {
474
            $this->state->singletons[$ctx->alias] = $instance;
475
        }
476
477
        // Register finalizer
478
        $finalizer = $this->getFinalizer($ctx, $instance);
479
        if ($finalizer !== null) {
480
            $this->state->finalizers[] = $finalizer;
481
        }
482
483
        return $instance;
484
    }
485
486
    /**
487
     * Check the class was configured as a singleton.
488
     */
489
    private function isSingleton(Ctx $ctx): bool
490
    {
491
        if ($ctx->singleton === true) {
492
            return true;
493
        }
494
495
        /** @psalm-suppress RedundantCondition https://github.com/vimeo/psalm/issues/9489 */
496
        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
            return true;
498
        }
499
500
        return $ctx->reflection->getAttributes(Singleton::class) !== [];
501
    }
502
503
    private function getFinalizer(Ctx $ctx, object $instance): ?callable
504
    {
505
        /**
506
         * @psalm-suppress UnnecessaryVarAnnotation
507
         * @var Finalize|null $attribute
508
         */
509
        $attribute = ($ctx->reflection->getAttributes(Finalize::class)[0] ?? null)?->newInstance();
510
        if ($attribute === null) {
511
            return null;
512
        }
513
514
        return [$instance, $attribute->method];
515
    }
516
517
    /**
518
     * Find and run inflector
519
     */
520
    private function runInflector(object $instance): object
521
    {
522
        $scope = $this->scope;
523
524
        while ($scope !== null) {
525
            foreach ($this->state->inflectors as $class => $inflectors) {
526
                if ($instance instanceof $class) {
527
                    foreach ($inflectors as $inflector) {
528
                        $instance = $inflector->getParametersCount() > 1
529
                            ? $this->invoker->invoke($inflector->inflector, [$instance])
530
                            : ($inflector->inflector)($instance);
531
                    }
532
                }
533
            }
534
535
            $scope = $scope->getParentScope();
536
        }
537
538
        return $instance;
539
    }
540
}
541