Passed
Pull Request — master (#1017)
by Maxim
12:07
created

Initializer   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 198
Duplicated Lines 0 %

Test Coverage

Coverage 98.92%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 43
eloc 76
dl 0
loc 198
ccs 92
cts 93
cp 0.9892
rs 8.96
c 1
b 0
f 1

12 Methods

Rating   Name   Duplication   Size   Complexity  
A findBootloaderClassesInMethod() 0 11 4
A initBindings() 0 8 3
A __construct() 0 6 1
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 29 11

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\BinderInterface;
19
use Spiral\Core\Container;
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
class Initializer implements InitializerInterface, Container\SingletonInterface
28
{
29
    protected ?BootloaderCheckerInterface $checker = null;
30
31 526
    public function __construct(
32
        protected readonly ContainerInterface $container,
33
        protected readonly BinderInterface $binder,
34
        protected readonly ClassesRegistry $bootloaders = new ClassesRegistry(),
35
        ?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

35
        /** @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...
36
    ) {
37 526
    }
38
39
    /**
40
     * Instantiate bootloader objects and resolve dependencies
41
     *
42
     * @param TClass[]|array<TClass, array<string,mixed>> $classes
43
     */
44 517
    public function init(array $classes, bool $useConfig = true): \Generator
45
    {
46 517
        $this->checker ??= $this->initDefaultChecker();
47
48 517
        foreach ($classes as $bootloader => $options) {
49
            // default bootload syntax as simple array
50 517
            if (\is_string($options) || $options instanceof BootloaderInterface) {
51 500
                $bootloader = $options;
52 500
                $options = [];
53
            }
54 517
            $options = $useConfig ? $this->getBootloadConfig($bootloader, $options) : [];
55
56 517
            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 390
                continue;
58
            }
59
60 496
            $this->bootloaders->register($bootloader instanceof BootloaderInterface ? $bootloader::class : $bootloader);
61
62 496
            if (!$bootloader instanceof BootloaderInterface) {
63 495
                $bootloader = $this->container->get($bootloader);
64
            }
65
66
            /** @var BootloaderInterface $bootloader */
67 496
            yield from $this->initBootloader($bootloader);
68 496
            yield $bootloader::class => [
69 496
                'bootloader' => $bootloader,
70 496
                'options' => $options instanceof BootloadConfig ? $options->args : $options,
71 496
            ];
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 496
    protected function initBootloader(BootloaderInterface $bootloader): iterable
84
    {
85 496
        if ($bootloader instanceof DependedInterface) {
86 496
            yield from $this->init($this->getDependencies($bootloader));
87
        }
88
89 496
        $this->initBindings(
90 496
            $bootloader->defineBindings(),
91 496
            $bootloader->defineSingletons()
92 496
        );
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 496
    protected function initBindings(array $bindings, array $singletons): void
102
    {
103 496
        foreach ($bindings as $aliases => $resolver) {
104 428
            $this->binder->bind($aliases, $resolver);
105
        }
106
107 496
        foreach ($singletons as $aliases => $resolver) {
108 473
            $this->binder->bindSingleton($aliases, $resolver);
109
        }
110
    }
111
112 496
    protected function getDependencies(DependedInterface $bootloader): array
113
    {
114 496
        $deps = $bootloader->defineDependencies();
115
116 496
        $reflectionClass = new \ReflectionClass($bootloader);
117
118 496
        $methodsDeps = [];
119
120 496
        foreach (Methods::cases() as $method) {
121 496
            if ($reflectionClass->hasMethod($method->value)) {
122 473
                $methodsDeps[] = $this->findBootloaderClassesInMethod(
123 473
                    $reflectionClass->getMethod($method->value)
124 473
                );
125
            }
126
        }
127
128 496
        return \array_values(\array_unique(\array_merge($deps, ...$methodsDeps)));
129
    }
130
131 473
    protected function findBootloaderClassesInMethod(\ReflectionMethod $method): array
132
    {
133 473
        $args = [];
134 473
        foreach ($method->getParameters() as $parameter) {
135 473
            $type = $parameter->getType();
136 473
            if ($type instanceof \ReflectionNamedType && $this->shouldBeBooted($type)) {
137 412
                $args[] = $type->getName();
138
            }
139
        }
140
141 473
        return $args;
142
    }
143
144 473
    protected function shouldBeBooted(\ReflectionNamedType $type): bool
145
    {
146
        /** @var TClass $class */
147 473
        $class = $type->getName();
148
149 473
        return $this->isBootloader($class)
150 473
            && !$this->bootloaders->isBooted($class);
151
    }
152
153
    /**
154
     * @psalm-pure
155
     * @psalm-assert-if-true TClass $class
156
     */
157 473
    protected function isBootloader(string|object $class): bool
158
    {
159 473
        return \is_subclass_of($class, BootloaderInterface::class);
160
    }
161
162 517
    protected function initDefaultChecker(): BootloaderCheckerInterface
163
    {
164 517
        $registry = new CheckerRegistry();
165 517
        $registry->register($this->container->get(ConfigChecker::class));
166 517
        $registry->register(new ClassExistsChecker());
167 517
        $registry->register(new CanBootedChecker($this->bootloaders));
168
169 517
        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 519
    private function getBootloadConfig(
178
        string|BootloaderInterface $bootloader,
179
        array|callable|BootloadConfig $config
180
    ): BootloadConfig {
181 519
        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 519
        $attr = $this->getBootloadConfigAttribute($bootloader);
187
188 519
        $getArgument = static function (string $key, bool $override, mixed $default = []) use ($config, $attr): mixed {
189 519
            return match (true) {
190 519
                $config instanceof BootloadConfig && $override => $config->{$key},
191 519
                $config instanceof BootloadConfig && !$override && \is_array($default) =>
192 519
                    $config->{$key} + ($attr->{$key} ?? []),
193 519
                $config instanceof BootloadConfig && !$override && \is_bool($default) => $config->{$key},
194 519
                \is_array($config) && $config !== [] && $key === 'args' => $config,
195 519
                default => $attr->{$key} ?? $default,
196 519
            };
197 519
        };
198
199 519
        $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...
200
201 519
        return new BootloadConfig(
202 519
            args: $getArgument('args', $override),
203 519
            enabled: $getArgument('enabled', $override, true),
204 519
            allowEnv: $getArgument('allowEnv', $override),
205 519
            denyEnv: $getArgument('denyEnv', $override),
206 519
        );
207
    }
208
209
    /**
210
     * @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...
211
     */
212 519
    private function getBootloadConfigAttribute(string|BootloaderInterface $bootloader): ?BootloadConfig
213
    {
214 519
        $attribute = null;
215 519
        if ($bootloader instanceof BootloaderInterface || \class_exists($bootloader)) {
216 518
            $ref = new \ReflectionClass($bootloader);
217 518
            $attribute = $ref->getAttributes(BootloadConfig::class)[0] ?? null;
218
        }
219
220 519
        if ($attribute === null) {
221 500
            return null;
222
        }
223
224 22
        return $attribute->newInstance();
225
    }
226
}
227