Passed
Push — add-extend-config ( f39ef4 )
by Chema
03:24
created

SetupGacela::getExtendConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

350
            $this->eventDispatcher->/** @scrutinizer ignore-call */ 
351
                                    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...
351
352 11
            foreach ($this->specificListeners ?? [] as $event => $listeners) {
353 5
                foreach ($listeners as $callable) {
354 5
                    $this->eventDispatcher->registerSpecificListener($event, $callable);
355
                }
356
            }
357
        } else {
358 25
            $this->eventDispatcher = new NullEventDispatcher();
359
        }
360
361 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...
362
    }
363
364
    /**
365
     * @return array<string,list<Closure>>
366
     */
367 99
    public function getServicesToExtend(): array
368
    {
369 99
        return (array)$this->servicesToExtend;
370
    }
371
372 78
    public function setFileCacheEnabled(?bool $flag): self
373
    {
374 78
        $this->markPropertyChanged(self::fileCacheEnabled, $flag);
375 78
        $this->fileCacheEnabled = $flag ?? self::DEFAULT_FILE_CACHE_ENABLED;
376
377 78
        return $this;
378
    }
379
380 60
    public function canCreateEventDispatcher(): bool
381
    {
382 60
        return $this->areEventListenersEnabled
383 60
            && $this->hasEventListeners();
384
    }
385
386
    /**
387
     * @param ?array<string,mixed> $configKeyValues
388
     */
389 78
    public function setConfigKeyValues(?array $configKeyValues): self
390
    {
391 78
        $this->markPropertyChanged(self::configKeyValues, $configKeyValues);
392 78
        $this->configKeyValues = $configKeyValues ?? self::DEFAULT_CONFIG_KEY_VALUES;
393
394 78
        return $this;
395
    }
396
397
    /**
398
     * @return array<class-string,list<callable>>|null
399
     */
400 3
    public function getSpecificListeners(): ?array
401
    {
402 3
        return $this->specificListeners;
403
    }
404
405
    /**
406
     * @return list<callable>|null
407
     */
408 3
    public function getGenericListeners(): ?array
409
    {
410 3
        return $this->genericListeners;
411
    }
412
413 27
    public function isPropertyChanged(string $name): bool
414
    {
415 27
        return $this->changedProperties[$name] ?? false;
416
    }
417
418 27
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): self
419
    {
420 27
        $this->eventDispatcher = $eventDispatcher;
421
422 27
        return $this;
423
    }
424
425 27
    public function combine(self $other): self
426
    {
427 27
        return (new SetupCombinator($this))->combine($other);
428
    }
429
430
    /**
431
     * @param list<Closure> $servicesToExtend
432
     */
433 1
    public function addServicesToExtend(string $serviceId, array $servicesToExtend): self
434
    {
435 1
        $this->servicesToExtend[$serviceId] ??= [];
436 1
        $this->servicesToExtend[$serviceId] = array_merge(
437 1
            $this->servicesToExtend[$serviceId],
438 1
            $servicesToExtend,
439 1
        );
440
441 1
        return $this;
442
    }
443
444 27
    public function combineExternalServices(array $list): void
445
    {
446 27
        $this->setExternalServices(array_merge($this->externalServices ?? [], $list));
447
    }
448
449 1
    public function combineProjectNamespaces(array $list): void
450
    {
451 1
        $this->setProjectNamespaces(array_merge($this->projectNamespaces ?? [], $list));
452
    }
453
454 1
    public function combineConfigKeyValues(array $list): void
455
    {
456 1
        $this->setConfigKeyValues(array_merge($this->configKeyValues ?? [], $list));
457
    }
458
459
    /**
460
     * @param list<class-string> $list
461
     */
462
    public function combineExtendConfig(array $list): void
463
    {
464
        $this->setExtendConfig(array_merge($this->extendConfig ?? [], $list));
465
    }
466
467
    /**
468
     * @param list<class-string> $list
469
     */
470 1
    public function combineAfterPlugins(array $list): void
471
    {
472 1
        $this->setAfterPlugins(array_merge($this->afterPlugins ?? [], $list));
473
    }
474
475
    /**
476
     * @return list<class-string>
477
     */
478
    public function getExtendConfig(): array
479
    {
480
        return (array)$this->extendConfig;
481
    }
482
483
    /**
484
     * @return list<class-string>
485
     */
486 87
    public function getAfterPlugins(): array
487
    {
488 87
        return (array)$this->afterPlugins;
489
    }
490
491 60
    private static function runExtendConfig(GacelaConfig $config): void
492
    {
493 60
        $plugins = $config->build()['before-config'] ?? [];
494
495 60
        if ($plugins === []) {
496 59
            return;
497
        }
498
499 1
        foreach ($plugins as $pluginName) {
500
            /** @var callable $plugin */
501 1
            $plugin = Container::create($pluginName);
502 1
            $plugin($config);
503
        }
504
    }
505
506 78
    private function setAreEventListenersEnabled(?bool $flag): self
507
    {
508 78
        $this->areEventListenersEnabled = $flag ?? self::DEFAULT_ARE_EVENT_LISTENERS_ENABLED;
509
510 78
        return $this;
511
    }
512
513 59
    private function hasEventListeners(): bool
514
    {
515 59
        return !empty($this->genericListeners)
516 59
            || !empty($this->specificListeners);
517
    }
518
519
    /**
520
     * @param ?list<callable> $listeners
521
     */
522 78
    private function setGenericListeners(?array $listeners): self
523
    {
524 78
        $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...
525
526 78
        return $this;
527
    }
528
529
    /**
530
     * @param ?array<string,list<Closure>> $list
531
     */
532 78
    private function setServicesToExtend(?array $list): self
533
    {
534 78
        $this->markPropertyChanged(self::servicesToExtend, $list);
535 78
        $this->servicesToExtend = $list ?? self::DEFAULT_SERVICES_TO_EXTEND;
536
537 78
        return $this;
538
    }
539
540
    /**
541
     * @param ?list<class-string> $list
542
     */
543 78
    private function setExtendConfig(?array $list): self
544
    {
545 78
        $this->markPropertyChanged(self::extendConfig, $list);
546 78
        $this->extendConfig = $list ?? self::DEFAULT_EXTEND_CONFIG;
1 ignored issue
show
Documentation Bug introduced by
It seems like $list ?? self::DEFAULT_EXTEND_CONFIG of type array is incompatible with the declared type Gacela\Framework\Bootstrap\list|null of property $extendConfig.

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...
547
548 78
        return $this;
549
    }
550
551
    /**
552
     * @param ?list<class-string> $list
553
     */
554 78
    private function setAfterPlugins(?array $list): self
555
    {
556 78
        $this->markPropertyChanged(self::afterPlugins, $list);
557 78
        $this->afterPlugins = $list ?? self::DEFAULT_PLUGINS;
1 ignored issue
show
Documentation Bug introduced by
It seems like $list ?? self::DEFAULT_PLUGINS of type array is incompatible with the declared type Gacela\Framework\Bootstrap\list|null of property $afterPlugins.

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...
558
559 78
        return $this;
560
    }
561
562
    /**
563
     * @param ?array<class-string,list<callable>> $listeners
564
     */
565 78
    private function setSpecificListeners(?array $listeners): self
566
    {
567 78
        $this->specificListeners = $listeners ?? self::DEFAULT_SPECIFIC_LISTENERS;
568
569 78
        return $this;
570
    }
571
572 81
    private function markPropertyChanged(string $name, mixed $value): void
573
    {
574 81
        $this->changedProperties[$name] = ($value !== null);
575
    }
576
}
577