Actor   F
last analyzed

Complexity

Total Complexity 101

Size/Duplication

Total Lines 627
Duplicated Lines 0 %

Test Coverage

Coverage 93.67%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 101
eloc 316
dl 0
loc 627
ccs 311
cts 332
cp 0.9367
rs 2
c 2
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveProxy() 0 18 4
A disableBinding() 0 3 1
A resolveAutowire() 0 18 4
A __construct() 0 12 1
A enableBinding() 0 3 1
A resolveBinding() 0 25 1
A validateConstraint() 0 12 4
A autowire() 0 14 6
B resolveWeakReference() 0 35 6
A resolveShared() 0 22 3
C resolveInjector() 0 58 11
B resolveAlias() 0 33 7
C resolveType() 0 55 12
B resolveFactory() 0 40 10
F createInstance() 0 106 14
A validateArguments() 0 9 2
A runInflector() 0 19 6
A isSingleton() 0 12 3
A registerInstance() 0 14 3
A getFinalizer() 0 12 2

How to fix   Complexity   

Complex Class

Complex classes like Actor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Actor, and based on these observations, apply Extract Interface, too.

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 Spiral\Core\BinderInterface;
11
use Spiral\Core\Config\Alias;
12
use Spiral\Core\Config\Binding;
13
use Spiral\Core\Attribute;
14
use Spiral\Core\Container\InjectorInterface;
15
use Spiral\Core\Container\SingletonInterface;
16
use Spiral\Core\Exception\Container\AutowireException;
17
use Spiral\Core\Exception\Container\ContainerException;
18
use Spiral\Core\Exception\Container\InjectionException;
19
use Spiral\Core\Exception\Container\NotCallableException;
20
use Spiral\Core\Exception\Container\NotFoundException;
21
use Spiral\Core\Exception\Container\RecursiveProxyException;
22
use Spiral\Core\Exception\Container\TracedContainerException;
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\Internal\Proxy\RetryContext;
31
use Spiral\Core\InvokerInterface;
32
use Spiral\Core\Options;
33
use Spiral\Core\ResolverInterface;
34
use Spiral\Core\Config;
35
36
/**
37
 * @internal
38
 */
39
final class Actor
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 FactoryInterface $factory;
49
    private Scope $scope;
50
    private Options $options;
51
52 1443
    public function __construct(Registry $constructor)
53
    {
54 1443
        $constructor->set('hub', $this);
55
56 1443
        $this->state = $constructor->get('state', State::class);
57 1443
        $this->binder = $constructor->get('binder', BinderInterface::class);
58 1443
        $this->invoker = $constructor->get('invoker', InvokerInterface::class);
59 1443
        $this->container = $constructor->get('container', ContainerInterface::class);
60 1443
        $this->resolver = $constructor->get('resolver', ResolverInterface::class);
61 1443
        $this->factory = $constructor->get('factory', FactoryInterface::class);
62 1443
        $this->scope = $constructor->get('scope', Scope::class);
63 1443
        $this->options = $constructor->getOptions();
64
    }
65
66 1108
    public function disableBinding(string $alias): void
67
    {
68 1108
        unset($this->state->bindings[$alias]);
69
    }
70
71 1108
    public function enableBinding(string $alias, Binding $binding): void
72
    {
73 1108
        $this->state->bindings[$alias] ??= $binding;
74
    }
75
76
    /**
77
     * Get class name of the resolving object.
78
     * With it, you can quickly get cached singleton or detect that there are injector or binding.
79
     * The method does not detect that the class is instantiable.
80
     *
81
     * @param non-empty-string $alias
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
82
     *
83
     * @param self|null $actor Will be set to the hub where the result was found.
84
     *
85
     * @return class-string|null Returns {@see null} if exactly one returning class cannot be resolved.
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|null at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|null.
Loading history...
86
     * @psalm-suppress all
87
     */
88 1241
    public function resolveType(
89
        string $alias,
90
        ?Binding &$binding = null,
91
        ?object &$singleton = null,
92
        ?object &$injector = null,
93
        ?self &$actor = null,
94
        bool $followAlias = true,
95
    ): ?string {
96
        // Aliases to prevent circular dependencies
97 1241
        $as = [];
98 1241
        $actor = $this;
99
        do {
100 1241
            $bindings = &$actor->state->bindings;
101 1241
            $singletons = &$actor->state->singletons;
102 1241
            $injectors = &$actor->state->injectors;
103 1241
            $binding = $bindings[$alias] ?? null;
104 1241
            if (\array_key_exists($alias, $singletons)) {
105 474
                $singleton = $singletons[$alias];
106 474
                $injector = $injectors[$alias] ?? null;
107 474
                return \is_object($singleton::class) ? $singleton::class : null;
108
            }
109
110 1241
            if ($binding !== null) {
111 1110
                if ($followAlias && $binding::class === Alias::class) {
112 81
                    if ($binding->alias === $alias) {
113 79
                        break;
114
                    }
115
116 2
                    $alias = $binding->alias;
117 2
                    \array_key_exists($alias, $as) and throw new ContainerException(
118 2
                        \sprintf('Circular dependency detected for alias `%s`.', $alias),
119 2
                    );
120 2
                    $as[$alias] = true;
121 2
                    continue;
122
                }
123
124 1109
                return $binding->getReturnClass();
125
            }
126
127 1093
            if (\array_key_exists($alias, $injectors)) {
128
                $injector = $injectors[$alias];
129
                $binding = $bindings[$alias] ?? null;
130
                return $alias;
131
            }
132
133
            // Go to parent scope
134 1093
            $parent = $actor->scope->getParentActor();
135 1093
            if ($parent === null) {
136 1084
                break;
137
            }
138
139 255
            $actor = $parent;
140 256
        } while (true);
141
142 1084
        return \class_exists($alias) ? $alias : null;
143
    }
144
145 1108
    public function resolveBinding(
146
        object $binding,
147
        string $alias,
148
        \Stringable|string|null $context,
149
        array $arguments,
150
        Tracer $tracer,
151
    ): mixed {
152 1108
        return match ($binding::class) {
153 846
            Config\Alias::class => $this->resolveAlias($binding, $alias, $context, $arguments, $tracer),
154 1108
            Config\Proxy::class,
155 393
            Config\DeprecationProxy::class => $this->resolveProxy($binding, $alias, $context),
156 9
            Config\Autowire::class => $this->resolveAutowire($binding, $alias, $context, $arguments, $tracer),
157 1108
            Config\DeferredFactory::class,
158 597
            Config\Factory::class => $this->resolveFactory($binding, $alias, $context, $arguments, $tracer),
159 911
            Config\Shared::class => $this->resolveShared($binding, $alias, $context, $arguments, $tracer),
160 477
            Config\Injectable::class => $this->resolveInjector(
161 477
                $binding,
162 477
                new Ctx(alias: $alias, class: $alias, context: $context),
163 477
                $arguments,
164 477
                $tracer,
165 477
            ),
166 2
            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...
167 735
            Config\WeakReference::class => $this
168 735
                ->resolveWeakReference($binding, $alias, $context, $arguments, $tracer),
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\Actor::resolveWeakReference(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

168
                ->resolveWeakReference(/** @scrutinizer ignore-type */ $binding, $alias, $context, $arguments, $tracer),
Loading history...
169 1097
            default => $binding,
170 1108
        };
171
    }
172
173
    /**
174
     * Automatically create class.
175
     * Object will be cached if the $arguments list is empty.
176
     *
177
     * @psalm-assert class-string $class
178
     *
179
     * @throws AutowireException
180
     * @throws \Throwable
181
     */
182 1086
    public function autowire(Ctx $ctx, array $arguments, ?Actor $fallbackActor, Tracer $tracer): object
183
    {
184 1086
        \class_exists($ctx->class)
185 1086
        or (\interface_exists($ctx->class)
186 1086
            && (isset($this->state->injectors[$ctx->class]) || $this->binder->hasInjector($ctx->class)))
187 1086
        or throw NotFoundException::createWithTrace(
188 1086
            $ctx->alias === $ctx->class
189 775
                ? "Can't autowire `$ctx->class`: class or injector not found."
190 775
                : "Can't resolve `$ctx->alias`: class or injector `$ctx->class` not found.",
191 1086
            $tracer->getTraces(),
192 1086
        );
193
194
        // automatically create instance
195 1075
        return $this->createInstance($ctx, $arguments, $fallbackActor, $tracer);
196
    }
197
198
    /**
199
     * @psalm-suppress UnusedParam
200
     * todo wat should we do with $arguments?
201
     */
202 529
    private function resolveInjector(Config\Injectable $binding, Ctx $ctx, array $arguments, Tracer $tracer)
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

202
    private function resolveInjector(Config\Injectable $binding, Ctx $ctx, /** @scrutinizer ignore-unused */ array $arguments, Tracer $tracer)

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...
203
    {
204 529
        $context = $ctx->context;
205
        try {
206 529
            $reflection = $ctx->reflection ??= new \ReflectionClass($ctx->class);
207
        } catch (\ReflectionException $e) {
208
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
209
        }
210
211 529
        $injector = $binding->injector;
212
213
        try {
214 529
            $injectorInstance = \is_object($injector) ? $injector : $this->container->get($injector);
215
216 527
            if (!$injectorInstance instanceof InjectorInterface) {
217 2
                throw new InjectionException(
218 2
                    \sprintf(
219 2
                        "Class '%s' must be an instance of InjectorInterface for '%s'.",
220 2
                        $injectorInstance::class,
221 2
                        $reflection->getName(),
222 2
                    ),
223 2
                );
224
            }
225
226
            /** @var array<class-string<InjectorInterface>, \ReflectionMethod|false> $cache reflection for extended injectors */
227 525
            static $cache = [];
228 525
            $extended = $cache[$injectorInstance::class] ??= (
229 525
            static fn(\ReflectionType $type): bool =>
230 17
                $type::class === \ReflectionUnionType::class || (string) $type === 'mixed'
231 525
            )(
232 525
                ($refMethod = new \ReflectionMethod($injectorInstance, 'createInjection'))
233 525
                    ->getParameters()[1]->getType()
234 525
            ) ? $refMethod : false;
235
236 525
            $asIs = $extended && (\is_string($context) || $this->validateArguments($extended, [$reflection, $context]));
237 525
            $instance = $injectorInstance->createInjection($reflection, match (true) {
238 525
                $asIs => $context,
239 513
                $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

239
                $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 Nette\Iterators\CachingIterator or RectorPrefix202504\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

239
                $context instanceof \ReflectionParameter => $context->/** @scrutinizer ignore-call */ getName(),
Loading history...
240 525
                default => (string) $context,
241 525
            });
242
243 523
            if (!$reflection->isInstance($instance)) {
244 1
                throw new InjectionException(
245 1
                    \sprintf(
246 1
                        "Invalid injection response for '%s'.",
247 1
                        $reflection->getName(),
248 1
                    ),
249 1
                );
250
            }
251
252 522
            return $instance;
253 8
        } catch (TracedContainerException $e) {
254 2
            throw isset($injectorInstance) ? $e : $e::createWithTrace(\sprintf(
255 2
                'Can\'t resolve `%s`.',
256 2
                $tracer->getRootAlias(),
257 2
            ), $tracer->getTraces(), $e);
258
        } finally {
259 529
            $this->state->bindings[$ctx->class] ??= $binding;
260
        }
261
    }
262
263 846
    private function resolveAlias(
264
        Config\Alias $binding,
265
        string $alias,
266
        \Stringable|string|null $context,
267
        array $arguments,
268
        Tracer $tracer,
269
    ): mixed {
270 846
        if ($binding->alias === $alias) {
271 487
            $instance = $this->autowire(
272 487
                new Ctx(alias: $alias, class: $binding->alias, context: $context, singleton: $binding->singleton && $arguments === []),
273 487
                $arguments,
274 487
                $this,
275 487
                $tracer,
276 487
            );
277
        } else {
278
            try {
279
                //Binding is pointing to something else
280 838
                $instance = $this->factory->make($binding->alias, $arguments, $context);
0 ignored issues
show
Unused Code introduced by
The call to Spiral\Core\FactoryInterface::make() has too many arguments starting with $context. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

280
                /** @scrutinizer ignore-call */ 
281
                $instance = $this->factory->make($binding->alias, $arguments, $context);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
281 3
            } catch (TracedContainerException $e) {
282 3
                throw $e::createWithTrace(
283 3
                    $alias === $tracer->getRootAlias()
284 3
                        ? "Can't resolve `{$alias}`."
285 3
                        : "Can't resolve `$alias` with alias `{$binding->alias}`.",
286 3
                    $tracer->getTraces(),
287 3
                    $e,
288 3
                );
289
            }
290
291 835
            $binding->singleton and $arguments === [] and $this->state->singletons[$alias] = $instance;
292
        }
293
294
295 843
        return $instance;
296
    }
297
298 393
    private function resolveProxy(Config\Proxy $binding, string $alias, \Stringable|string|null $context): mixed
299
    {
300 393
        if ($context instanceof RetryContext) {
301 58
            return $binding->fallbackFactory === null
302 1
                ? throw new RecursiveProxyException(
303 1
                    $alias,
304 1
                    $this->scope->getScopeName(),
305 1
                )
306 57
                : ($binding->fallbackFactory)($this->container, $context->context);
307
        }
308
309 393
        $result = Proxy::create(new \ReflectionClass($binding->getReturnClass()), $context, new Attribute\Proxy());
310
311 393
        if ($binding->singleton) {
312 44
            $this->state->singletons[$alias] = $result;
313
        }
314
315 393
        return $result;
316
    }
317
318 911
    private function resolveShared(
319
        Config\Shared $binding,
320
        string $alias,
321
        \Stringable|string|null $context,
322
        array $arguments,
323
        Tracer $tracer,
324
    ): object {
325 911
        if ($arguments !== []) {
326
            // Avoid singleton cache
327 1
            return $this->createInstance(
328 1
                new Ctx(alias: $alias, class: $binding->value::class, context: $context, singleton: false),
329 1
                $arguments,
330 1
                $this,
331 1
                $tracer,
332 1
            );
333
        }
334
335 911
        if ($binding->singleton) {
336 682
            $this->state->singletons[$alias] = $binding->value;
337
        }
338
339 911
        return $binding->value;
340
    }
341
342 9
    private function resolveAutowire(
343
        Config\Autowire $binding,
344
        string $alias,
345
        \Stringable|string|null $context,
346
        array $arguments,
347
        Tracer $tracer,
348
    ): mixed {
349 9
        $target = $binding->autowire->alias;
350 9
        $ctx = new Ctx(alias: $alias, class: $target, context: $context, singleton: $binding->singleton && $arguments === [] ?: null);
351
352 9
        if ($alias === $target) {
353 3
            $instance = $this->autowire($ctx, \array_merge($binding->autowire->parameters, $arguments), $this, $tracer);
354
        } else {
355 6
            $instance = $binding->autowire->resolve($this->factory, $arguments);
356 6
            $this->validateConstraint($instance, $ctx);
357
        }
358
359 9
        return $this->registerInstance($ctx, $instance);
360
    }
361
362 597
    private function resolveFactory(
363
        Config\Factory|Config\DeferredFactory $binding,
364
        string $alias,
365
        \Stringable|string|null $context,
366
        array $arguments,
367
        Tracer $tracer,
368
    ): mixed {
369 597
        $ctx = new Ctx(alias: $alias, class: $alias, context: $context, singleton: $binding->singleton && $arguments === [] ?: null);
370
        try {
371 597
            $instance = $binding::class === 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

371
            $instance = $binding::class === 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...
372 26
                ? ($binding->factory)()
373 573
                : $this->invoker->invoke($binding->factory, $arguments);
374 15
        } catch (NotCallableException $e) {
375
            throw TracedContainerException::createWithTrace(
376
                \sprintf('Invalid callable binding for `%s`.', $ctx->alias),
377
                $tracer->getTraces(),
378
                $e,
379
            );
380 15
        } catch (TracedContainerException $e) {
381 2
            throw $e::createWithTrace(
382 2
                \sprintf("Can't resolve `%s`: factory invocation failed.", $tracer->getRootAlias()),
383 2
                $tracer->getTraces(),
384 2
                $e,
385 2
            );
386 13
        } catch (ContainerExceptionInterface $e) {
387 5
            throw $e;
388 8
        } catch (\Throwable $e) {
389 8
            throw NotFoundException::createWithTrace(
390 8
                \sprintf("Can't resolve `%s` due to factory invocation error: %s", $tracer->getRootAlias(), $e->getMessage()),
391 8
                $tracer->getTraces(),
392 8
                $e,
393 8
            );
394
        }
395
396 590
        if (\is_object($instance)) {
397 585
            $this->validateConstraint($instance, $ctx);
398 584
            return $this->registerInstance($ctx, $instance);
399
        }
400
401 5
        return $instance;
402
    }
403
404 735
    private function resolveWeakReference(
405
        Config\WeakReference $binding,
406
        string $alias,
407
        \Stringable|string|null $context,
408
        array $arguments,
409
        Tracer $tracer,
410
    ): ?object {
411 735
        $avoidCache = $arguments !== [];
412
413 735
        if (($avoidCache || $binding->reference->get() === null) && \class_exists($alias)) {
414
            try {
415 3
                $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

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

415
                $tracer->push(false, alias: $alias, source: \WeakReference::class, /** @scrutinizer ignore-type */ context: $context);
Loading history...
416
417 3
                $object = $this->createInstance(
418 3
                    new Ctx(alias: $alias, class: $alias, context: $context, singleton: false),
419 3
                    $arguments,
420 3
                    $this,
421 3
                    $tracer,
422 3
                );
423 3
                if ($avoidCache) {
424 1
                    return $object;
425
                }
426 2
                $binding->reference = \WeakReference::create($object);
427
            } catch (\Throwable) {
428
                throw TracedContainerException::createWithTrace(\sprintf(
429
                    'Can\'t resolve `%s`: can\'t instantiate `%s` from WeakReference binding.',
430
                    $tracer->getRootAlias(),
431
                    $alias,
432
                ), $tracer->getTraces());
433
            } finally {
434 3
                $tracer->pop();
435
            }
436
        }
437
438 735
        return $binding->reference->get();
439
    }
440
441
    /**
442
     * @throws BadScopeException
443
     * @throws \Throwable
444
     */
445 591
    private function validateConstraint(
446
        object $instance,
447
        Ctx $ctx,
448
    ): void {
449 591
        if ($this->options->checkScope) {
450
            // Check scope name
451 2
            $ctx->reflection ??= new \ReflectionClass($instance);
452 2
            $scopeName = ($ctx->reflection->getAttributes(Attribute\Scope::class)[0] ?? null)?->newInstance()->name;
453 2
            if ($scopeName !== null) {
454 2
                $scope = $this->scope;
455 2
                while ($scope->getScopeName() !== $scopeName) {
456 2
                    $scope = $scope->getParentScope() ?? throw new BadScopeException($scopeName, $instance::class);
457
                }
458
            }
459
        }
460
    }
461
462
    /**
463
     * Create instance of desired class.
464
     *
465
     * @template TObject of object
466
     *
467
     * @param Ctx<TObject> $ctx
468
     * @param array $arguments Constructor arguments.
469
     *
470
     * @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...
471
     *
472
     * @throws ContainerException
473
     * @throws \Throwable
474
     */
475 1079
    private function createInstance(
476
        Ctx $ctx,
477
        array $arguments,
478
        ?Actor $fallbackActor,
479
        Tracer $tracer,
480
    ): object {
481 1079
        $class = $ctx->class;
482
        try {
483 1079
            $ctx->reflection = $reflection = new \ReflectionClass($class);
484
        } catch (\ReflectionException $e) {
485
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
486
        }
487
488
        // Check Scope attribute
489 1079
        $actor = $fallbackActor ?? $this;
490 1079
        if ($this->options->checkScope) { # todo
491 6
            $ar = ($reflection->getAttributes(Attribute\Scope::class)[0] ?? null);
492 6
            if ($ar !== null) {
493
                /** @var Attribute\Scope $attr */
494 4
                $attr = $ar->newInstance();
495 4
                $scope = $this->scope;
496 4
                $actor = $this;
497
                // Go through all parent scopes
498 4
                $needed = $actor;
499 4
                while ($attr->name !== $scope->getScopeName()) {
500 3
                    $needed = $scope->getParentActor();
501 3
                    if ($needed === null) {
502 1
                        throw new BadScopeException($attr->name, $class);
503
                    }
504
505 2
                    $scope = $scope->getParentScope();
506
                }
507
508
                // Scope found
509 3
                $actor = $needed;
510
            }
511
        } # todo
512
513
        // We have to construct class using external injector when we know the exact context
514 1078
        if ($arguments === [] && $actor->binder->hasInjector($class)) {
515 512
            return $actor->resolveInjector($actor->state->bindings[$ctx->class], $ctx, $arguments, $tracer);
516
        }
517
518 1070
        if (!$reflection->isInstantiable()) {
519 8
            $itIs = match (true) {
520 8
                $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

520
                $reflection->/** @scrutinizer ignore-call */ 
521
                             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...
521 2
                $reflection->isAbstract() => 'Abstract class',
522 1
                default => 'Class',
523 8
            };
524 8
            throw TracedContainerException::createWithTrace(
525 8
                \sprintf('%s `%s` can not be constructed.', $itIs, $class),
526 8
                $tracer->getTraces(),
527 8
            );
528
        }
529
530 1069
        $constructor = $reflection->getConstructor();
531
532 1069
        if ($constructor !== null) {
533
            try {
534 942
                $newScope = $this !== $actor;
535 942
                $debug = [
536 942
                    'action' => 'resolve arguments',
537 942
                    'alias' => $ctx->class,
538 942
                    'signature' => $constructor,
539 942
                ];
540 942
                $newScope and $debug += [
541 942
                    'jump to scope' => $actor->scope->getScopeName(),
542 942
                    'from scope' => $this->scope->getScopeName(),
543 942
                ];
544 942
                $tracer->push($newScope, ...$debug);
545 942
                $tracer->push(true);
546 942
                $args = $actor->resolver->resolveArguments($constructor, $arguments, $actor->options->validateArguments);
547 23
            } catch (\Throwable $e) {
548 23
                throw TracedContainerException::createWithTrace(
549 23
                    \sprintf(
550 23
                        "Can't resolve `%s`.",
551 23
                        $tracer->getRootAlias(),
552 23
                    ), $tracer->getTraces(), $e
553 23
                );
554
            } finally {
555 942
                $tracer->pop($newScope);
556 942
                $tracer->pop(false);
557
            }
558
            try {
559
                // Using constructor with resolved arguments
560 921
                $tracer->push(false, call: "$class::__construct", arguments: $args);
0 ignored issues
show
Bug introduced by
$class.'::__construct' 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

560
                $tracer->push(false, /** @scrutinizer ignore-type */ call: "$class::__construct", arguments: $args);
Loading history...
561 921
                $tracer->push(true);
562 921
                $instance = new $class(...$args);
563 2
            } catch (\TypeError $e) {
564 1
                throw new WrongTypeException($constructor, $e);
565 1
            } catch (TracedContainerException $e) {
566 1
                throw $e::createWithTrace(\sprintf(
567 1
                    'Can\'t resolve `%s`: failed constructing `%s`.',
568 1
                    $tracer->getRootAlias(),
569 1
                    $class,
570 1
                ), $tracer->getTraces(), $e);
571
            } finally {
572 921
                $tracer->pop(true);
573 921
                $tracer->pop(false);
574
            }
575
        } else {
576
            // No constructor specified
577 873
            $instance = $reflection->newInstance();
578
        }
579
580 1046
        return $actor->registerInstance($ctx, $instance);
581
    }
582
583
    /**
584
     * Register instance in container, might perform methods like auto-singletons, log populations, etc.
585
     */
586 1063
    private function registerInstance(Ctx $ctx, object $instance): object
587
    {
588 1063
        $ctx->reflection ??= new \ReflectionClass($instance);
589
590 1063
        $instance = $this->runInflector($instance);
591
592
        // Declarative singletons
593 1063
        $this->isSingleton($ctx) and $this->state->singletons[$ctx->alias] = $instance;
594
595
        // Register finalizer
596 1063
        $finalizer = $this->getFinalizer($ctx, $instance);
597 1063
        $finalizer === null or $this->state->finalizers[] = $finalizer;
598
599 1063
        return $instance;
600
    }
601
602
    /**
603
     * Check the class was configured as a singleton.
604
     */
605 1063
    private function isSingleton(Ctx $ctx): bool
606
    {
607 1063
        if (is_bool($ctx->singleton)) {
608 645
            return $ctx->singleton;
609
        }
610
611
        /** @psalm-suppress RedundantCondition https://github.com/vimeo/psalm/issues/9489 */
612 1030
        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

612
        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...
613
            return true;
614
        }
615
616 1030
        return $ctx->reflection->getAttributes(Attribute\Singleton::class) !== [];
617
    }
618
619 1063
    private function getFinalizer(Ctx $ctx, object $instance): ?callable
620
    {
621
        /**
622
         * @psalm-suppress UnnecessaryVarAnnotation
623
         * @var Attribute\Finalize|null $attribute
624
         */
625 1063
        $attribute = ($ctx->reflection->getAttributes(Attribute\Finalize::class)[0] ?? null)?->newInstance();
626 1063
        if ($attribute === null) {
627 1062
            return null;
628
        }
629
630 4
        return [$instance, $attribute->method];
631
    }
632
633
    /**
634
     * Find and run inflector
635
     */
636 1063
    private function runInflector(object $instance): object
637
    {
638 1063
        $scope = $this->scope;
639
640 1063
        while ($scope !== null) {
641 1063
            foreach ($this->state->inflectors as $class => $inflectors) {
642 5
                if ($instance instanceof $class) {
643 5
                    foreach ($inflectors as $inflector) {
644 5
                        $instance = $inflector->getParametersCount() > 1
645 1
                            ? $this->invoker->invoke($inflector->inflector, [$instance])
646 4
                            : ($inflector->inflector)($instance);
647
                    }
648
                }
649
            }
650
651 1063
            $scope = $scope->getParentScope();
652
        }
653
654 1063
        return $instance;
655
    }
656
657 12
    private function validateArguments(ContextFunction $reflection, array $arguments = []): bool
658
    {
659
        try {
660 12
            $this->resolver->validateArguments($reflection, $arguments);
661
        } catch (\Throwable) {
662
            return false;
663
        }
664
665 12
        return true;
666
    }
667
}
668