Passed
Push — feature/change-setup-override-... ( 7b5c3a...d078d0 )
by Chema
03:52
created

SetupGacela::combineExternalServices()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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

323
            $this->eventDispatcher->/** @scrutinizer ignore-call */ 
324
                                    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...
324
325 8
            foreach ($this->specificListeners ?? [] as $event => $listeners) {
326 5
                foreach ($listeners as $callable) {
327 5
                    $this->eventDispatcher->registerSpecificListener($event, $callable);
328
                }
329
            }
330
        } else {
331 12
            $this->eventDispatcher = new NullEventDispatcher();
332
        }
333
334 20
        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...
335
    }
336
337 65
    public function setAreEventListenersEnabled(?bool $flag): self
338
    {
339 65
        $this->markPropertyChanged('areEventListenersEnabled', $flag);
340 65
        $this->areEventListenersEnabled = $flag ?? self::DEFAULT_ARE_EVENT_LISTENERS_ENABLED;
341
342 65
        return $this;
343
    }
344
345 25
    public function combine(self $other): self
346
    {
347 25
        $this->overrideResetInMemoryCache($other);
348 25
        $this->overrideFileCacheSettings($other);
349
350 25
        $this->combineExternalServices($other);
351 25
        $this->combineProjectNamespaces($other);
352 25
        $this->combineConfigKeyValues($other);
353 25
        $this->combineEventDispatcher($other);
354
355 25
        return $this;
356
    }
357
358 25
    private function combineExternalServices(self $other): void
359
    {
360 25
        if ($other->isPropertyChanged('externalServices')) {
361 25
            $this->externalServices = array_merge($this->externalServices ?? [], $other->externalServices());
362
        }
363
    }
364
365 25
    private function overrideResetInMemoryCache(self $other): void
366
    {
367 25
        if ($other->isPropertyChanged('shouldResetInMemoryCache')) {
368 1
            $this->shouldResetInMemoryCache = $other->shouldResetInMemoryCache();
369
        }
370
    }
371
372 25
    private function overrideFileCacheSettings(self $other): void
373
    {
374 25
        if ($other->isPropertyChanged('fileCacheEnabled')) {
375 1
            $this->fileCacheEnabled = $other->isFileCacheEnabled();
376
        }
377 25
        if ($other->isPropertyChanged('fileCacheDirectory')) {
378 1
            $this->fileCacheDirectory = $other->getFileCacheDirectory();
379
        }
380
    }
381
382 25
    private function combineProjectNamespaces(self $other): void
383
    {
384 25
        if ($other->isPropertyChanged('projectNamespaces')) {
385 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...
386
        }
387
    }
388
389 25
    private function combineConfigKeyValues(self $other): void
390
    {
391 25
        if ($other->isPropertyChanged('configKeyValues')) {
392 1
            $this->configKeyValues = array_merge($this->configKeyValues ?? [], $other->getConfigKeyValues());
393
        }
394
    }
395
396 25
    private function combineEventDispatcher(self $other): void
397
    {
398 25
        if ($other->canCreateEventDispatcher()) {
399 3
            if (!($this->eventDispatcher instanceof ConfigurableEventDispatcher)) {
400 2
                $this->eventDispatcher = new ConfigurableEventDispatcher();
401
            }
402 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

402
            $this->eventDispatcher->/** @scrutinizer ignore-call */ 
403
                                    registerGenericListeners((array)$other->genericListeners);
Loading history...
403
404 3
            foreach ($other->specificListeners ?? [] as $event => $listeners) {
405 2
                foreach ($listeners as $callable) {
406 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

406
                    $this->eventDispatcher->/** @scrutinizer ignore-call */ 
407
                                            registerSpecificListener($event, $callable);
Loading history...
407
                }
408
            }
409
        } else {
410 22
            $this->eventDispatcher = new NullEventDispatcher();
411
        }
412
    }
413
414 42
    private function canCreateEventDispatcher(): bool
415
    {
416 42
        return $this->areEventListenersEnabled
417 42
            && $this->hasEventListeners();
418
    }
419
420 41
    private function hasEventListeners(): bool
421
    {
422 41
        return !empty($this->genericListeners)
423 41
            || !empty($this->specificListeners);
424
    }
425
426
    /**
427
     * @param array<string,mixed> $configKeyValues
428
     */
429 65
    private function setConfigKeyValues(?array $configKeyValues): self
430
    {
431 65
        $this->markPropertyChanged('configKeyValues', $configKeyValues);
432 65
        $this->configKeyValues = $configKeyValues ?? self::DEFAULT_CONFIG_KEY_VALUES;
433
434 65
        return $this;
435
    }
436
437
    /**
438
     * @param list<callable> $listeners
439
     */
440 65
    private function setGenericListeners(?array $listeners): self
441
    {
442 65
        $this->markPropertyChanged('genericListeners', $listeners);
443 65
        $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...
444
445 65
        return $this;
446
    }
447
448
    /**
449
     * @param array<class-string,list<callable>> $listeners
0 ignored issues
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...
450
     */
451 65
    private function setSpecificListeners(?array $listeners): self
452
    {
453 65
        $this->markPropertyChanged('specificListeners', $listeners);
454 65
        $this->specificListeners = $listeners ?? self::DEFAULT_SPECIFIC_LISTENERS;
455
456 65
        return $this;
457
    }
458
459
    /**
460
     * @param mixed $value
461
     */
462 70
    private function markPropertyChanged(string $name, $value): void
463
    {
464 70
        $this->changedProperties[$name] = ($value !== null);
465
    }
466
467 25
    private function isPropertyChanged(string $name): bool
468
    {
469 25
        return $this->changedProperties[$name] ?? false;
470
    }
471
}
472