Passed
Push — refactor/extract-setup-gacela-... ( db7b60 )
by Chema
03:57
created

SetupGacela::combineEventDispatcher()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 7
nop 1
dl 0
loc 15
ccs 9
cts 9
cp 1
crap 5
rs 9.6111
c 0
b 0
f 0

2 Methods

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

324
            $this->eventDispatcher->/** @scrutinizer ignore-call */ 
325
                                    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...
325
326
            foreach ($this->specificListeners ?? [] as $event => $listeners) {
327
                foreach ($listeners as $callable) {
328
                    $this->eventDispatcher->registerSpecificListener($event, $callable);
329
                }
330
            }
331
        } else {
332
            $this->eventDispatcher = new NullEventDispatcher();
333
        }
334
335
        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...
336
    }
337
338
    /**
339
     * @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...
340
     */
341
    public function getServicesToExtend(): array
342
    {
343
        return (array)$this->servicesToExtend;
344
    }
345
346
    public function setFileCacheEnabled(?bool $flag): self
347
    {
348
        $this->markPropertyChanged('fileCacheEnabled', $flag);
349
        $this->fileCacheEnabled = $flag ?? self::DEFAULT_FILE_CACHE_ENABLED;
350
351
        return $this;
352
    }
353
354
    public function canCreateEventDispatcher(): bool
355
    {
356
        return $this->areEventListenersEnabled
357
            && $this->hasEventListeners();
358
    }
359
360
    /**
361
     * @param ?array<string,mixed> $configKeyValues
362
     */
363
    public function setConfigKeyValues(?array $configKeyValues): self
364
    {
365
        $this->markPropertyChanged('configKeyValues', $configKeyValues);
366
        $this->configKeyValues = $configKeyValues ?? self::DEFAULT_CONFIG_KEY_VALUES;
367
368
        return $this;
369
    }
370
371
    /**
372
     * @return array<class-string,list<callable>>|null
1 ignored issue
show
Documentation Bug introduced by
The doc comment array<class-string,list<callable>>|null at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string,list<callable>>|null.
Loading history...
373
     */
374
    public function getSpecificListeners(): ?array
375
    {
376
        return $this->specificListeners;
377
    }
378
379
    /**
380
     * @return list<callable>|null
381
     */
382
    public function getGenericListeners(): ?array
383
    {
384
        return $this->genericListeners;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this->genericListeners also could return the type array which is incompatible with the documented return type Gacela\Framework\Bootstrap\list.
Loading history...
385
    }
386
387
    public function isPropertyChanged(string $name): bool
388
    {
389
        return $this->changedProperties[$name] ?? false;
390
    }
391
392
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): self
393
    {
394
        $this->eventDispatcher = $eventDispatcher;
395
396
        return $this;
397
    }
398
399
    public function combine(self $other): self
400
    {
401
        return (new SetupCombiner($this))->combine($other);
402
    }
403
404
    /**
405
     * @param list<Closure> $servicesToExtend
406
     */
407
    public function addServicesToExtend(string $serviceId, array $servicesToExtend): self
408
    {
409
        $this->servicesToExtend[$serviceId] ??= [];
410
        $this->servicesToExtend[$serviceId] = array_merge(
411
            $this->servicesToExtend[$serviceId],
412
            $servicesToExtend,
413
        );
414
415
        return $this;
416
    }
417
418
    public function combineExternalServices(array $list): void
419
    {
420
        $this->setExternalServices(array_merge($this->externalServices ?? [], $list));
421
    }
422
423
    public function combineProjectNamespaces(array $list): void
424
    {
425
        $this->setProjectNamespaces(array_merge($this->projectNamespaces ?? [], $list));
426
    }
427
428
    public function combineConfigKeyValues(array $list): void
429
    {
430
        $this->setConfigKeyValues(array_merge($this->configKeyValues ?? [], $list));
431
    }
432
433
    private function setAreEventListenersEnabled(?bool $flag): self
434
    {
435
        $this->markPropertyChanged('areEventListenersEnabled', $flag);
436
        $this->areEventListenersEnabled = $flag ?? self::DEFAULT_ARE_EVENT_LISTENERS_ENABLED;
437
438
        return $this;
439
    }
440
441
    private function hasEventListeners(): bool
442
    {
443
        return !empty($this->genericListeners)
444
            || !empty($this->specificListeners);
445
    }
446
447
    /**
448
     * @param ?list<callable> $listeners
449
     */
450
    private function setGenericListeners(?array $listeners): self
451
    {
452
        $this->markPropertyChanged('genericListeners', $listeners);
453
        $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...
454
455
        return $this;
456
    }
457
458
    /**
459
     * @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...
460
     */
461
    private function setServicesToExtend(?array $list): self
462
    {
463
        $this->markPropertyChanged('servicesToExtend', $list);
464
        $this->servicesToExtend = $list ?? self::DEFAULT_SERVICES_TO_EXTEND;
465
466
        return $this;
467
    }
468
469
    /**
470
     * @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...
471
     */
472
    private function setSpecificListeners(?array $listeners): self
473
    {
474
        $this->markPropertyChanged('specificListeners', $listeners);
475
        $this->specificListeners = $listeners ?? self::DEFAULT_SPECIFIC_LISTENERS;
476
477
        return $this;
478
    }
479
480
    private function markPropertyChanged(string $name, mixed $value): void
481
    {
482
        $this->changedProperties[$name] = ($value !== null);
483
    }
484
}
485