Passed
Pull Request — master (#247)
by Chema
05:47 queued 02:59
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

1 Method

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

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