Initializer   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 196
Duplicated Lines 0 %

Test Coverage

Coverage 98.9%

Importance

Changes 0
Metric Value
wmc 43
eloc 76
dl 0
loc 196
ccs 90
cts 91
cp 0.989
rs 8.96
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A findBootloaderClassesInMethod() 0 11 4
A initBindings() 0 8 3
B init() 0 27 10
A getRegistry() 0 3 1
A getDependencies() 0 17 3
A initDefaultChecker() 0 8 1
A initBootloader() 0 9 2
A isBootloader() 0 3 1
A shouldBeBooted() 0 7 2
A getBootloadConfigAttribute() 0 13 4
B getBootloadConfig() 0 27 11
A __construct() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Initializer 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 Initializer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Boot\BootloadManager;
6
7
use Psr\Container\ContainerInterface;
8
use Spiral\Boot\Attribute\BootloadConfig;
9
use Spiral\Boot\Bootloader\BootloaderInterface;
10
use Spiral\Boot\Bootloader\DependedInterface;
11
use Spiral\Boot\BootloadManager\Checker\BootloaderChecker;
12
use Spiral\Boot\BootloadManager\Checker\BootloaderCheckerInterface;
13
use Spiral\Boot\BootloadManager\Checker\CanBootedChecker;
14
use Spiral\Boot\BootloadManager\Checker\CheckerRegistry;
15
use Spiral\Boot\BootloadManager\Checker\ClassExistsChecker;
16
use Spiral\Boot\BootloadManager\Checker\ConfigChecker;
17
use Spiral\Boot\BootloadManagerInterface;
18
use Spiral\Core\Attribute\Singleton;
19
use Spiral\Core\BinderInterface;
20
use Spiral\Core\ResolverInterface;
21
22
/**
23
 * @internal
24
 * @psalm-import-type TClass from BootloadManagerInterface
25
 * @psalm-import-type TFullBinding from BootloaderInterface
26
 */
27
#[Singleton]
28
class Initializer implements InitializerInterface
29
{
30
    protected ?BootloaderCheckerInterface $checker = null;
31
32 632
    public function __construct(
33
        protected readonly ContainerInterface $container,
34
        protected readonly BinderInterface $binder,
35
        protected readonly ClassesRegistry $bootloaders = new ClassesRegistry(),
36
        ?BootloaderCheckerInterface $checker = null,
0 ignored issues
show
Unused Code introduced by
The parameter $checker 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

36
        /** @scrutinizer ignore-unused */ ?BootloaderCheckerInterface $checker = null,

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...
37 632
    ) {}
38
39
    /**
40
     * Instantiate bootloader objects and resolve dependencies
41
     *
42
     * @param TClass[]|array<class-string<BootloaderInterface>, array<string,mixed>> $classes
0 ignored issues
show
Documentation Bug introduced by
The doc comment TClass[]|array<class-str...>, array<string,mixed>> at position 6 could not be parsed: Unknown type name 'class-string' at position 6 in TClass[]|array<class-string<BootloaderInterface>, array<string,mixed>>.
Loading history...
43
     */
44 623
    public function init(array $classes, bool $useConfig = true): \Generator
45
    {
46 623
        $this->checker ??= $this->initDefaultChecker();
47
48 623
        foreach ($classes as $bootloader => $options) {
49
            // default bootload syntax as simple array
50 623
            if (\is_string($options) || $options instanceof BootloaderInterface) {
51 606
                $bootloader = $options;
52 606
                $options = [];
53
            }
54 623
            $options = $useConfig ? $this->getBootloadConfig($bootloader, $options) : [];
55
56 623
            if (!$this->checker->canInitialize($bootloader, $useConfig ? $options : null)) {
0 ignored issues
show
Bug introduced by
It seems like $useConfig ? $options : null can also be of type array; however, parameter $config of Spiral\Boot\BootloadMana...erface::canInitialize() does only seem to accept Spiral\Boot\Attribute\BootloadConfig|null, maybe add an additional type check? ( Ignorable by Annotation )

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

56
            if (!$this->checker->canInitialize($bootloader, /** @scrutinizer ignore-type */ $useConfig ? $options : null)) {
Loading history...
Bug introduced by
The method canInitialize() 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

56
            if (!$this->checker->/** @scrutinizer ignore-call */ canInitialize($bootloader, $useConfig ? $options : null)) {

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...
57 457
                continue;
58
            }
59
60 602
            $this->bootloaders->register($bootloader instanceof BootloaderInterface ? $bootloader::class : $bootloader);
61
62 602
            if (!$bootloader instanceof BootloaderInterface) {
63 601
                $bootloader = $this->container->get($bootloader);
64
            }
65
66
            /** @var BootloaderInterface $bootloader */
67 602
            yield from $this->initBootloader($bootloader);
68 602
            yield $bootloader::class => [
69 602
                'bootloader' => $bootloader,
70 602
                'options' => $options instanceof BootloadConfig ? $options->args : $options,
71 602
            ];
72
        }
73
    }
74
75 9
    public function getRegistry(): ClassesRegistry
76
    {
77 9
        return $this->bootloaders;
78
    }
79
80
    /**
81
     * Resolve all bootloader dependencies and init bindings
82
     */
83 602
    protected function initBootloader(BootloaderInterface $bootloader): iterable
84
    {
85 602
        if ($bootloader instanceof DependedInterface) {
86 602
            yield from $this->init($this->getDependencies($bootloader));
87
        }
88
89 602
        $this->initBindings(
90 602
            $bootloader->defineBindings(),
91 602
            $bootloader->defineSingletons(),
92 602
        );
93
    }
94
95
    /**
96
     * Bind declared bindings.
97
     *
98
     * @param TFullBinding $bindings
0 ignored issues
show
Bug introduced by
The type Spiral\Boot\BootloadManager\TFullBinding 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...
99
     * @param TFullBinding $singletons
100
     */
101 602
    protected function initBindings(array $bindings, array $singletons): void
102
    {
103 602
        foreach ($bindings as $aliases => $resolver) {
104 558
            $this->binder->bind($aliases, $resolver);
105
        }
106
107 602
        foreach ($singletons as $aliases => $resolver) {
108 579
            $this->binder->bindSingleton($aliases, $resolver);
109
        }
110
    }
111
112 602
    protected function getDependencies(DependedInterface $bootloader): array
113
    {
114 602
        $deps = $bootloader->defineDependencies();
115
116 602
        $reflectionClass = new \ReflectionClass($bootloader);
117
118 602
        $methodsDeps = [];
119
120 602
        foreach (Methods::cases() as $method) {
121 602
            if ($reflectionClass->hasMethod($method->value)) {
122 579
                $methodsDeps[] = $this->findBootloaderClassesInMethod(
123 579
                    $reflectionClass->getMethod($method->value),
124 579
                );
125
            }
126
        }
127
128 602
        return \array_values(\array_unique(\array_merge($deps, ...$methodsDeps)));
129
    }
130
131 579
    protected function findBootloaderClassesInMethod(\ReflectionMethod $method): array
132
    {
133 579
        $args = [];
134 579
        foreach ($method->getParameters() as $parameter) {
135 579
            $type = $parameter->getType();
136 579
            if ($type instanceof \ReflectionNamedType && $this->shouldBeBooted($type)) {
137 460
                $args[] = $type->getName();
138
            }
139
        }
140
141 579
        return $args;
142
    }
143
144 579
    protected function shouldBeBooted(\ReflectionNamedType $type): bool
145
    {
146
        /** @var TClass $class */
147 579
        $class = $type->getName();
148
149 579
        return $this->isBootloader($class)
150 579
            && !$this->bootloaders->isBooted($class);
151
    }
152
153
    /**
154
     * @psalm-pure
155
     * @psalm-assert-if-true TClass $class
156
     */
157 579
    protected function isBootloader(string|object $class): bool
158
    {
159 579
        return \is_subclass_of($class, BootloaderInterface::class);
160
    }
161
162 623
    protected function initDefaultChecker(): BootloaderCheckerInterface
163
    {
164 623
        $registry = new CheckerRegistry();
165 623
        $registry->register($this->container->get(ConfigChecker::class));
166 623
        $registry->register(new ClassExistsChecker());
167 623
        $registry->register(new CanBootedChecker($this->bootloaders));
168
169 623
        return new BootloaderChecker($registry);
170
    }
171
172
    /**
173
     * Returns merged config. Attribute config have lower priority.
174
     *
175
     * @param class-string<BootloaderInterface>|BootloaderInterface $bootloader
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<BootloaderI...ce>|BootloaderInterface at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<BootloaderInterface>|BootloaderInterface.
Loading history...
176
     */
177 625
    private function getBootloadConfig(
178
        string|BootloaderInterface $bootloader,
179
        array|callable|BootloadConfig $config,
180
    ): BootloadConfig {
181 625
        if ($config instanceof \Closure) {
0 ignored issues
show
introduced by
$config is never a sub-type of Closure.
Loading history...
182 2
            $config = $this->container instanceof ResolverInterface
183 2
                ? $config(...$this->container->resolveArguments(new \ReflectionFunction($config)))
184
                : $config();
185
        }
186 625
        $attr = $this->getBootloadConfigAttribute($bootloader);
187
188 625
        $getArgument = static fn(string $key, bool $override, mixed $default = []): mixed => match (true) {
189 625
            $config instanceof BootloadConfig && $override => $config->{$key},
190 607
            $config instanceof BootloadConfig && !$override && \is_array($default) =>
191 3
                $config->{$key} + ($attr->{$key} ?? []),
192 607
            $config instanceof BootloadConfig && !$override && \is_bool($default) => $config->{$key},
193 604
            \is_array($config) && $config !== [] && $key === 'args' => $config,
194 625
            default => $attr->{$key} ?? $default,
195 607
        };
196
197 625
        $override = $config instanceof BootloadConfig ? $config->override : true;
0 ignored issues
show
introduced by
$config is never a sub-type of Spiral\Boot\Attribute\BootloadConfig.
Loading history...
198
199 625
        return new BootloadConfig(
200 625
            args: $getArgument('args', $override),
201 625
            enabled: $getArgument('enabled', $override, true),
202 625
            allowEnv: $getArgument('allowEnv', $override),
203 625
            denyEnv: $getArgument('denyEnv', $override),
204 625
        );
205
    }
206
207
    /**
208
     * @param class-string<BootloaderInterface>|BootloaderInterface $bootloader
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<BootloaderI...ce>|BootloaderInterface at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<BootloaderInterface>|BootloaderInterface.
Loading history...
209
     */
210 625
    private function getBootloadConfigAttribute(string|BootloaderInterface $bootloader): ?BootloadConfig
211
    {
212 625
        $attribute = null;
213 625
        if ($bootloader instanceof BootloaderInterface || \class_exists($bootloader)) {
214 624
            $ref = new \ReflectionClass($bootloader);
215 624
            $attribute = $ref->getAttributes(BootloadConfig::class)[0] ?? null;
216
        }
217
218 625
        if ($attribute === null) {
219 606
            return null;
220
        }
221
222 22
        return $attribute->newInstance();
223
    }
224
}
225