Passed
Push — improve-extend-config-containe... ( dba478...cb4d60 )
by Chema
03:10
created

SetupGacela::setBindingsFn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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

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

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