Passed
Push — setup-uses-abstract-parent-met... ( cefcd8 )
by Chema
03:44
created

SetupGacela   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 469
Duplicated Lines 0 %

Test Coverage

Coverage 95.86%

Importance

Changes 9
Bugs 1 Features 0
Metric Value
eloc 157
c 9
b 1
f 0
dl 0
loc 469
ccs 162
cts 169
cp 0.9586
rs 6.4799
wmc 54

43 Methods

Rating   Name   Duplication   Size   Complexity  
A setSuffixTypesBuilder() 0 5 1
A setConfigBuilder() 0 5 1
A setMappingInterfacesBuilder() 0 5 1
A __construct() 0 8 1
A fromGacelaConfig() 0 18 1
A setConfigFn() 0 5 1
A fromCallable() 0 6 1
A setExternalServices() 0 6 1
A fromFile() 0 13 3
A markPropertyChanged() 0 3 1
A setGenericListeners() 0 5 1
A setAreEventListenersEnabled() 0 5 1
A setServicesToExtend() 0 6 1
A getFileCacheDirectory() 0 3 1
A getConfigKeyValues() 0 3 1
A hasEventListeners() 0 4 2
A buildConfig() 0 11 2
A setFileCacheEnabled() 0 6 1
A isFileCacheEnabled() 0 3 1
A isPropertyChanged() 0 3 1
A addServicesToExtend() 0 9 1
A combineConfigKeyValues() 0 3 1
A setSuffixTypesFn() 0 5 1
A buildMappingInterfaces() 0 16 2
A combineProjectNamespaces() 0 3 1
A getSpecificListeners() 0 3 1
A setMappingInterfacesFn() 0 5 1
A setConfigKeyValues() 0 6 1
A getEventDispatcher() 0 20 5
A getProjectNamespaces() 0 3 1
A setSpecificListeners() 0 5 1
A canCreateEventDispatcher() 0 4 2
A setShouldResetInMemoryCache() 0 6 1
A shouldResetInMemoryCache() 0 3 1
A getGenericListeners() 0 3 1
A externalServices() 0 5 1
A setFileCacheDirectory() 0 6 1
A combine() 0 3 1
A getServicesToExtend() 0 3 1
A setEventDispatcher() 0 5 1
A combineExternalServices() 0 3 1
A setProjectNamespaces() 0 6 1
A buildSuffixTypes() 0 11 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework\Bootstrap;
6
7
use Closure;
8
use Gacela\Framework\ClassResolver\Cache\GacelaFileCache;
9
use Gacela\Framework\Config\GacelaConfigBuilder\ConfigBuilder;
10
use Gacela\Framework\Config\GacelaConfigBuilder\MappingInterfacesBuilder;
11
use Gacela\Framework\Config\GacelaConfigBuilder\SuffixTypesBuilder;
12
use Gacela\Framework\Event\Dispatcher\ConfigurableEventDispatcher;
13
use Gacela\Framework\Event\Dispatcher\EventDispatcherInterface;
14
use Gacela\Framework\Event\Dispatcher\NullEventDispatcher;
15
use RuntimeException;
16
17
use function is_callable;
18
19
/**
20
 * @psalm-suppress ArgumentTypeCoercion,MixedArgumentTypeCoercion
21
 */
22
final class SetupGacela extends AbstractSetupGacela
23
{
24
    public const shouldResetInMemoryCache = 'shouldResetInMemoryCache';
25
    public const fileCacheEnabled = 'fileCacheEnabled';
26
    public const fileCacheDirectory = 'fileCacheDirectory';
27
    public const externalServices = 'externalServices';
28
    public const projectNamespaces = 'projectNamespaces';
29
    public const configKeyValues = 'configKeyValues';
30
    public const servicesToExtend = 'servicesToExtend';
31
32
    private const DEFAULT_ARE_EVENT_LISTENERS_ENABLED = true;
33
    private const DEFAULT_SHOULD_RESET_IN_MEMORY_CACHE = false;
34
    private const DEFAULT_FILE_CACHE_ENABLED = GacelaFileCache::DEFAULT_ENABLED_VALUE;
35
    private const DEFAULT_FILE_CACHE_DIRECTORY = GacelaFileCache::DEFAULT_DIRECTORY_VALUE;
36
    private const DEFAULT_PROJECT_NAMESPACES = [];
37
    private const DEFAULT_CONFIG_KEY_VALUES = [];
38
    private const DEFAULT_GENERIC_LISTENERS = [];
39
    private const DEFAULT_SPECIFIC_LISTENERS = [];
40
    private const DEFAULT_SERVICES_TO_EXTEND = [];
41
42
    /** @var callable(ConfigBuilder):void */
43
    private $configFn;
44
45
    /** @var callable(MappingInterfacesBuilder,array<string,mixed>):void */
46
    private $mappingInterfacesFn;
47
48
    /** @var callable(SuffixTypesBuilder):void */
49
    private $suffixTypesFn;
50
51
    /** @var ?array<string,class-string|object|callable> */
52
    private ?array $externalServices = null;
53
54
    private ?ConfigBuilder $configBuilder = null;
55
56
    private ?SuffixTypesBuilder $suffixTypesBuilder = null;
57
58
    private ?MappingInterfacesBuilder $mappingInterfacesBuilder = null;
59
60
    private ?bool $shouldResetInMemoryCache = null;
61
62
    private ?bool $fileCacheEnabled = null;
63
64
    private ?string $fileCacheDirectory = null;
65
66
    /** @var ?list<string> */
67
    private ?array $projectNamespaces = null;
68
69
    /** @var ?array<string,mixed> */
70
    private ?array $configKeyValues = null;
71
72
    private ?bool $areEventListenersEnabled = null;
73
74
    /** @var ?list<callable> */
75
    private ?array $genericListeners = null;
76
77
    /** @var ?array<class-string,list<callable>> */
78
    private ?array $specificListeners = null;
79
80
    private ?EventDispatcherInterface $eventDispatcher = null;
81
82
    /** @var ?array<string,bool> */
83
    private ?array $changedProperties = null;
84
85
    /** @var ?array<string,list<Closure>> */
86
    private ?array $servicesToExtend = null;
87
88 109
    public function __construct()
89
    {
90 109
        $emptyFn = static function (): void {
91 61
        };
92
93 109
        $this->configFn = $emptyFn;
94 109
        $this->mappingInterfacesFn = $emptyFn;
95 109
        $this->suffixTypesFn = $emptyFn;
96
    }
97
98
    public static function fromFile(string $gacelaFilePath): self
99
    {
100
        if (!is_file($gacelaFilePath)) {
101
            throw new RuntimeException("Invalid file path: '{$gacelaFilePath}'");
102
        }
103
104
        /** @var callable(GacelaConfig):void|null $setupGacelaFileFn */
105
        $setupGacelaFileFn = include $gacelaFilePath;
106
        if (!is_callable($setupGacelaFileFn)) {
107
            return new self();
108
        }
109
110
        return self::fromCallable($setupGacelaFileFn);
111
    }
112
113
    /**
114
     * @param callable(GacelaConfig):void $setupGacelaFileFn
115
     */
116 56
    public static function fromCallable(callable $setupGacelaFileFn): self
117
    {
118 56
        $gacelaConfig = new GacelaConfig();
119 56
        $setupGacelaFileFn($gacelaConfig);
120
121 56
        return self::fromGacelaConfig($gacelaConfig);
122
    }
123
124 73
    public static function fromGacelaConfig(GacelaConfig $gacelaConfig): self
125
    {
126 73
        $build = $gacelaConfig->build();
127
128 73
        return (new self())
129 73
            ->setExternalServices($build['external-services'])
130 73
            ->setConfigBuilder($build['config-builder'])
131 73
            ->setSuffixTypesBuilder($build['suffix-types-builder'])
132 73
            ->setMappingInterfacesBuilder($build['mapping-interfaces-builder'])
133 73
            ->setShouldResetInMemoryCache($build['should-reset-in-memory-cache'])
134 73
            ->setFileCacheEnabled($build['file-cache-enabled'])
135 73
            ->setFileCacheDirectory($build['file-cache-directory'])
136 73
            ->setProjectNamespaces($build['project-namespaces'])
137 73
            ->setConfigKeyValues($build['config-key-values'])
138 73
            ->setAreEventListenersEnabled($build['are-event-listeners-enabled'])
139 73
            ->setGenericListeners($build['generic-listeners'])
140 73
            ->setSpecificListeners($build['specific-listeners'])
141 73
            ->setServicesToExtend($build['services-to-extend']);
142
    }
143
144
    /**
145
     * @param array<string,class-string|object|callable> $array
146
     */
147 76
    public function setExternalServices(array $array): self
148
    {
149 76
        $this->markPropertyChanged(self::externalServices, true);
150 76
        $this->externalServices = $array;
151
152 76
        return $this;
153
    }
154
155 73
    public function setConfigBuilder(ConfigBuilder $builder): self
156
    {
157 73
        $this->configBuilder = $builder;
158
159 73
        return $this;
160
    }
161
162 73
    public function setSuffixTypesBuilder(SuffixTypesBuilder $builder): self
163
    {
164 73
        $this->suffixTypesBuilder = $builder;
165
166 73
        return $this;
167
    }
168
169 73
    public function setMappingInterfacesBuilder(MappingInterfacesBuilder $builder): self
170
    {
171 73
        $this->mappingInterfacesBuilder = $builder;
172
173 73
        return $this;
174
    }
175
176
    /**
177
     * @param callable(ConfigBuilder):void $callable
178
     */
179 3
    public function setConfigFn(callable $callable): self
180
    {
181 3
        $this->configFn = $callable;
182
183 3
        return $this;
184
    }
185
186 62
    public function buildConfig(ConfigBuilder $builder): ConfigBuilder
187
    {
188 62
        $builder = parent::buildConfig($builder);
189
190 62
        if ($this->configBuilder) {
191 53
            $builder = $this->configBuilder;
192
        }
193
194 62
        ($this->configFn)($builder);
195
196 62
        return $builder;
197
    }
198
199
    /**
200
     * @param callable(MappingInterfacesBuilder,array<string,mixed>):void $callable
201
     */
202 3
    public function setMappingInterfacesFn(callable $callable): self
203
    {
204 3
        $this->mappingInterfacesFn = $callable;
205
206 3
        return $this;
207
    }
208
209
    /**
210
     * Define the mapping between interfaces and concretions, so Gacela services will auto-resolve them automatically.
211
     *
212
     * @param array<string,class-string|object|callable> $externalServices
213
     */
214 62
    public function buildMappingInterfaces(
215
        MappingInterfacesBuilder $builder,
216
        array $externalServices,
217
    ): MappingInterfacesBuilder {
218 62
        $builder = parent::buildMappingInterfaces($builder, $externalServices);
219
220 62
        if ($this->mappingInterfacesBuilder) {
221 53
            $builder = $this->mappingInterfacesBuilder;
222
        }
223
224 62
        ($this->mappingInterfacesFn)(
225 62
            $builder,
226 62
            array_merge($this->externalServices ?? [], $externalServices)
227 62
        );
228
229 62
        return $builder;
230
    }
231
232
    /**
233
     * @param callable(SuffixTypesBuilder):void $callable
234
     */
235 3
    public function setSuffixTypesFn(callable $callable): self
236
    {
237 3
        $this->suffixTypesFn = $callable;
238
239 3
        return $this;
240
    }
241
242
    /**
243
     * Allow overriding gacela resolvable types.
244
     */
245 62
    public function buildSuffixTypes(SuffixTypesBuilder $builder): SuffixTypesBuilder
246
    {
247 62
        $builder = parent::buildSuffixTypes($builder);
248
249 62
        if ($this->suffixTypesBuilder) {
250 53
            $builder = $this->suffixTypesBuilder;
251
        }
252
253 62
        ($this->suffixTypesFn)($builder);
254
255 62
        return $builder;
256
    }
257
258
    /**
259
     * @return array<string, class-string|object|callable>
1 ignored issue
show
Documentation Bug introduced by
The doc comment array<string, class-string|object|callable> at position 4 could not be parsed: Unknown type name 'class-string' at position 4 in array<string, class-string|object|callable>.
Loading history...
260
     */
261 26
    public function externalServices(): array
262
    {
263 26
        return array_merge(
264 26
            parent::externalServices(),
265 26
            $this->externalServices ?? [],
266 26
        );
267
    }
268
269 73
    public function setShouldResetInMemoryCache(?bool $flag): self
270
    {
271 73
        $this->markPropertyChanged(self::shouldResetInMemoryCache, $flag);
272 73
        $this->shouldResetInMemoryCache = $flag ?? self::DEFAULT_SHOULD_RESET_IN_MEMORY_CACHE;
273
274 73
        return $this;
275
    }
276
277 85
    public function shouldResetInMemoryCache(): bool
278
    {
279 85
        return (bool)$this->shouldResetInMemoryCache;
280
    }
281
282 41
    public function isFileCacheEnabled(): bool
283
    {
284 41
        return (bool)$this->fileCacheEnabled;
285
    }
286
287 6
    public function getFileCacheDirectory(): string
288
    {
289 6
        return (string)$this->fileCacheDirectory;
290
    }
291
292 73
    public function setFileCacheDirectory(?string $dir): self
293
    {
294 73
        $this->markPropertyChanged(self::fileCacheDirectory, $dir);
295 73
        $this->fileCacheDirectory = $dir ?? self::DEFAULT_FILE_CACHE_DIRECTORY;
296
297 73
        return $this;
298
    }
299
300
    /**
301
     * @param ?list<string> $list
302
     */
303 73
    public function setProjectNamespaces(?array $list): self
304
    {
305 73
        $this->markPropertyChanged(self::projectNamespaces, $list);
306 73
        $this->projectNamespaces = $list ?? self::DEFAULT_PROJECT_NAMESPACES;
1 ignored issue
show
Documentation Bug introduced by
It seems like $list ?? self::DEFAULT_PROJECT_NAMESPACES of type array is incompatible with the declared type Gacela\Framework\Bootstrap\list|null of property $projectNamespaces.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
307
308 73
        return $this;
309
    }
310
311
    /**
312
     * @return list<string>
313
     */
314 40
    public function getProjectNamespaces(): array
315
    {
316 40
        return (array)$this->projectNamespaces;
317
    }
318
319
    /**
320
     * @return array<string,mixed>
321
     */
322 85
    public function getConfigKeyValues(): array
323
    {
324 85
        return (array)$this->configKeyValues;
325
    }
326
327 50
    public function getEventDispatcher(): EventDispatcherInterface
328
    {
329 50
        if ($this->eventDispatcher !== null) {
330 18
            return $this->eventDispatcher;
331
        }
332
333 36
        if ($this->canCreateEventDispatcher()) {
334 11
            $this->eventDispatcher = new ConfigurableEventDispatcher();
335 11
            $this->eventDispatcher->registerGenericListeners($this->genericListeners ?? []);
1 ignored issue
show
Bug introduced by
The method registerGenericListeners() 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

335
            $this->eventDispatcher->/** @scrutinizer ignore-call */ 
336
                                    registerGenericListeners($this->genericListeners ?? []);

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...
336
337 11
            foreach ($this->specificListeners ?? [] as $event => $listeners) {
338 5
                foreach ($listeners as $callable) {
339 5
                    $this->eventDispatcher->registerSpecificListener($event, $callable);
340
                }
341
            }
342
        } else {
343 25
            $this->eventDispatcher = new NullEventDispatcher();
344
        }
345
346 36
        return $this->eventDispatcher;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->eventDispatcher returns the type null which is incompatible with the type-hinted return Gacela\Framework\Event\D...ventDispatcherInterface.
Loading history...
347
    }
348
349
    /**
350
     * @return array<string,list<Closure>>
351
     */
352 35
    public function getServicesToExtend(): array
353
    {
354 35
        return (array)$this->servicesToExtend;
355
    }
356
357 73
    public function setFileCacheEnabled(?bool $flag): self
358
    {
359 73
        $this->markPropertyChanged(self::fileCacheEnabled, $flag);
360 73
        $this->fileCacheEnabled = $flag ?? self::DEFAULT_FILE_CACHE_ENABLED;
361
362 73
        return $this;
363
    }
364
365 59
    public function canCreateEventDispatcher(): bool
366
    {
367 59
        return $this->areEventListenersEnabled
368 59
            && $this->hasEventListeners();
369
    }
370
371
    /**
372
     * @param ?array<string,mixed> $configKeyValues
373
     */
374 73
    public function setConfigKeyValues(?array $configKeyValues): self
375
    {
376 73
        $this->markPropertyChanged(self::configKeyValues, $configKeyValues);
377 73
        $this->configKeyValues = $configKeyValues ?? self::DEFAULT_CONFIG_KEY_VALUES;
378
379 73
        return $this;
380
    }
381
382
    /**
383
     * @return array<class-string,list<callable>>|null
384
     */
385 3
    public function getSpecificListeners(): ?array
386
    {
387 3
        return $this->specificListeners;
388
    }
389
390
    /**
391
     * @return list<callable>|null
392
     */
393 3
    public function getGenericListeners(): ?array
394
    {
395 3
        return $this->genericListeners;
396
    }
397
398 26
    public function isPropertyChanged(string $name): bool
399
    {
400 26
        return $this->changedProperties[$name] ?? false;
401
    }
402
403 26
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): self
404
    {
405 26
        $this->eventDispatcher = $eventDispatcher;
406
407 26
        return $this;
408
    }
409
410 26
    public function combine(self $other): self
411
    {
412 26
        return (new SetupCombinator($this))->combine($other);
413
    }
414
415
    /**
416
     * @param list<Closure> $servicesToExtend
417
     */
418 1
    public function addServicesToExtend(string $serviceId, array $servicesToExtend): self
419
    {
420 1
        $this->servicesToExtend[$serviceId] ??= [];
421 1
        $this->servicesToExtend[$serviceId] = array_merge(
422 1
            $this->servicesToExtend[$serviceId],
423 1
            $servicesToExtend,
424 1
        );
425
426 1
        return $this;
427
    }
428
429 26
    public function combineExternalServices(array $list): void
430
    {
431 26
        $this->setExternalServices(array_merge($this->externalServices ?? [], $list));
432
    }
433
434 1
    public function combineProjectNamespaces(array $list): void
435
    {
436 1
        $this->setProjectNamespaces(array_merge($this->projectNamespaces ?? [], $list));
437
    }
438
439 1
    public function combineConfigKeyValues(array $list): void
440
    {
441 1
        $this->setConfigKeyValues(array_merge($this->configKeyValues ?? [], $list));
442
    }
443
444 73
    private function setAreEventListenersEnabled(?bool $flag): self
445
    {
446 73
        $this->areEventListenersEnabled = $flag ?? self::DEFAULT_ARE_EVENT_LISTENERS_ENABLED;
447
448 73
        return $this;
449
    }
450
451 57
    private function hasEventListeners(): bool
452
    {
453 57
        return !empty($this->genericListeners)
454 57
            || !empty($this->specificListeners);
455
    }
456
457
    /**
458
     * @param ?list<callable> $listeners
459
     */
460 73
    private function setGenericListeners(?array $listeners): self
461
    {
462 73
        $this->genericListeners = $listeners ?? self::DEFAULT_GENERIC_LISTENERS;
1 ignored issue
show
Documentation Bug introduced by
It seems like $listeners ?? self::DEFAULT_GENERIC_LISTENERS of type array is incompatible with the declared type Gacela\Framework\Bootstrap\list|null of property $genericListeners.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
463
464 73
        return $this;
465
    }
466
467
    /**
468
     * @param ?array<string,list<Closure>> $list
469
     */
470 73
    private function setServicesToExtend(?array $list): self
471
    {
472 73
        $this->markPropertyChanged(self::servicesToExtend, $list);
473 73
        $this->servicesToExtend = $list ?? self::DEFAULT_SERVICES_TO_EXTEND;
474
475 73
        return $this;
476
    }
477
478
    /**
479
     * @param ?array<class-string,list<callable>> $listeners
480
     */
481 73
    private function setSpecificListeners(?array $listeners): self
482
    {
483 73
        $this->specificListeners = $listeners ?? self::DEFAULT_SPECIFIC_LISTENERS;
484
485 73
        return $this;
486
    }
487
488 76
    private function markPropertyChanged(string $name, mixed $value): void
489
    {
490 76
        $this->changedProperties[$name] = ($value !== null);
491
    }
492
}
493