Passed
Push — master ( de6296...7ea759 )
by Chema
03:29
created

SetupGacela::getConfigKeyValues()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

321
            $this->eventDispatcher->/** @scrutinizer ignore-call */ 
322
                                    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...
322
323 11
            foreach ($this->specificListeners ?? [] as $event => $listeners) {
324 5
                foreach ($listeners as $callable) {
325 5
                    $this->eventDispatcher->registerSpecificListener($event, $callable);
326
                }
327
            }
328
        } else {
329 17
            $this->eventDispatcher = new NullEventDispatcher();
330
        }
331
332 28
        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...
333
    }
334
335 26
    public function combine(self $other): self
336
    {
337 26
        $this->overrideResetInMemoryCache($other);
338 26
        $this->overrideFileCacheSettings($other);
339
340 26
        $this->combineExternalServices($other);
341 26
        $this->combineProjectNamespaces($other);
342 26
        $this->combineConfigKeyValues($other);
343 26
        $this->combineEventDispatcher($other);
344 26
        $this->combineServicesToExtend($other);
345
346 26
        return $this;
347
    }
348
349
    /**
350
     * @return array<string,list<Closure>>
1 ignored issue
show
Documentation Bug introduced by
The doc comment array<string,list<Closure>> at position 4 could not be parsed: Expected '>' at position 4, but found 'list'.
Loading history...
351
     */
352 33
    public function getServicesToExtend(): array
353
    {
354 33
        return (array)$this->servicesToExtend;
355
    }
356
357
    /**
358
     * @param ?array<string,list<Closure>> $list
1 ignored issue
show
Documentation Bug introduced by
The doc comment ?array<string,list<Closure>> at position 4 could not be parsed: Expected '>' at position 4, but found 'list'.
Loading history...
359
     */
360 72
    public function setServicesToExtend(?array $list): self
361
    {
362 72
        $this->markPropertyChanged('servicesToExtend', $list);
363 72
        $this->servicesToExtend = $list ?? self::DEFAULT_SERVICES_TO_EXTEND;
364
365 72
        return $this;
366
    }
367
368 72
    private function setFileCacheEnabled(?bool $flag): self
369
    {
370 72
        $this->markPropertyChanged('fileCacheEnabled', $flag);
371 72
        $this->fileCacheEnabled = $flag ?? self::DEFAULT_FILE_CACHE_ENABLED;
372
373 72
        return $this;
374
    }
375
376 72
    private function setAreEventListenersEnabled(?bool $flag): self
377
    {
378 72
        $this->markPropertyChanged('areEventListenersEnabled', $flag);
379 72
        $this->areEventListenersEnabled = $flag ?? self::DEFAULT_ARE_EVENT_LISTENERS_ENABLED;
380
381 72
        return $this;
382
    }
383
384 26
    private function combineExternalServices(self $other): void
385
    {
386 26
        if ($other->isPropertyChanged('externalServices')) {
387 26
            $this->externalServices = array_merge($this->externalServices ?? [], $other->externalServices());
388
        }
389
    }
390
391 26
    private function overrideResetInMemoryCache(self $other): void
392
    {
393 26
        if ($other->isPropertyChanged('shouldResetInMemoryCache')) {
394 1
            $this->shouldResetInMemoryCache = $other->shouldResetInMemoryCache();
395
        }
396
    }
397
398 26
    private function overrideFileCacheSettings(self $other): void
399
    {
400 26
        if ($other->isPropertyChanged('fileCacheEnabled')) {
401 1
            $this->fileCacheEnabled = $other->isFileCacheEnabled();
402
        }
403 26
        if ($other->isPropertyChanged('fileCacheDirectory')) {
404 1
            $this->fileCacheDirectory = $other->getFileCacheDirectory();
405
        }
406
    }
407
408 26
    private function combineProjectNamespaces(self $other): void
409
    {
410 26
        if ($other->isPropertyChanged('projectNamespaces')) {
411 1
            $this->projectNamespaces = array_merge($this->projectNamespaces ?? [], $other->getProjectNamespaces());
1 ignored issue
show
Documentation Bug introduced by
It seems like array_merge($this->proje...getProjectNamespaces()) 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...
412
        }
413
    }
414
415 26
    private function combineConfigKeyValues(self $other): void
416
    {
417 26
        if ($other->isPropertyChanged('configKeyValues')) {
418 1
            $this->configKeyValues = array_merge($this->configKeyValues ?? [], $other->getConfigKeyValues());
419
        }
420
    }
421
422 26
    private function combineEventDispatcher(self $other): void
423
    {
424 26
        if ($other->canCreateEventDispatcher()) {
425 3
            if (!($this->eventDispatcher instanceof ConfigurableEventDispatcher)) {
426 2
                $this->eventDispatcher = new ConfigurableEventDispatcher();
427
            }
428 3
            $this->eventDispatcher->registerGenericListeners((array)$other->genericListeners);
0 ignored issues
show
Bug introduced by
The method registerGenericListeners() does not exist on Gacela\Framework\Event\D...ventDispatcherInterface. It seems like you code against a sub-type of Gacela\Framework\Event\D...ventDispatcherInterface such as Gacela\Framework\Event\D...igurableEventDispatcher. ( Ignorable by Annotation )

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

428
            $this->eventDispatcher->/** @scrutinizer ignore-call */ 
429
                                    registerGenericListeners((array)$other->genericListeners);
Loading history...
429
430 3
            foreach ($other->specificListeners ?? [] as $event => $listeners) {
431 2
                foreach ($listeners as $callable) {
432 2
                    $this->eventDispatcher->registerSpecificListener($event, $callable);
0 ignored issues
show
Bug introduced by
The method registerSpecificListener() does not exist on Gacela\Framework\Event\D...ventDispatcherInterface. It seems like you code against a sub-type of Gacela\Framework\Event\D...ventDispatcherInterface such as Gacela\Framework\Event\D...igurableEventDispatcher. ( Ignorable by Annotation )

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

432
                    $this->eventDispatcher->/** @scrutinizer ignore-call */ 
433
                                            registerSpecificListener($event, $callable);
Loading history...
433
                }
434
            }
435
        } else {
436 23
            $this->eventDispatcher = new NullEventDispatcher();
437
        }
438
    }
439
440 26
    private function combineServicesToExtend(self $other): void
441
    {
442 26
        if ($other->isPropertyChanged('servicesToExtend')) {
443 26
            foreach ($other->getServicesToExtend() as $serviceId => $otherServiceToExtend) {
444 1
                $this->servicesToExtend[$serviceId] ??= [];
445 1
                $this->servicesToExtend[$serviceId] = array_merge(
446 1
                    $this->servicesToExtend[$serviceId],
447
                    $otherServiceToExtend
448
                );
449
            }
450
        }
451
    }
452
453 51
    private function canCreateEventDispatcher(): bool
454
    {
455 51
        return $this->areEventListenersEnabled
456 51
            && $this->hasEventListeners();
457
    }
458
459 51
    private function hasEventListeners(): bool
460
    {
461 51
        return !empty($this->genericListeners)
462 51
            || !empty($this->specificListeners);
463
    }
464
465
    /**
466
     * @param ?array<string,mixed> $configKeyValues
467
     */
468 72
    private function setConfigKeyValues(?array $configKeyValues): self
469
    {
470 72
        $this->markPropertyChanged('configKeyValues', $configKeyValues);
471 72
        $this->configKeyValues = $configKeyValues ?? self::DEFAULT_CONFIG_KEY_VALUES;
472
473 72
        return $this;
474
    }
475
476
    /**
477
     * @param ?list<callable> $listeners
478
     */
479 72
    private function setGenericListeners(?array $listeners): self
480
    {
481 72
        $this->markPropertyChanged('genericListeners', $listeners);
482 72
        $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...
483
484 72
        return $this;
485
    }
486
487
    /**
488
     * @param ?array<class-string,list<callable>> $listeners
1 ignored issue
show
Documentation Bug introduced by
The doc comment ?array<class-string,list<callable>> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in ?array<class-string,list<callable>>.
Loading history...
489
     */
490 72
    private function setSpecificListeners(?array $listeners): self
491
    {
492 72
        $this->markPropertyChanged('specificListeners', $listeners);
493 72
        $this->specificListeners = $listeners ?? self::DEFAULT_SPECIFIC_LISTENERS;
494
495 72
        return $this;
496
    }
497
498
    /**
499
     * @param mixed $value
500
     */
501 77
    private function markPropertyChanged(string $name, $value): void
502
    {
503 77
        $this->changedProperties[$name] = ($value !== null);
504
    }
505
506 26
    private function isPropertyChanged(string $name): bool
507
    {
508 26
        return $this->changedProperties[$name] ?? false;
509
    }
510
}
511