Passed
Push — rename-plugins-to-after-plugin... ( ebb4f8 )
by Chema
03:39
created

SetupGacela::setAfterPlugins()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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

342
            $this->eventDispatcher->/** @scrutinizer ignore-call */ 
343
                                    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...
343
344 11
            foreach ($this->specificListeners ?? [] as $event => $listeners) {
345 5
                foreach ($listeners as $callable) {
346 5
                    $this->eventDispatcher->registerSpecificListener($event, $callable);
347
                }
348
            }
349
        } else {
350 24
            $this->eventDispatcher = new NullEventDispatcher();
351
        }
352
353 35
        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...
354
    }
355
356
    /**
357
     * @return array<string,list<Closure>>
358
     */
359 37
    public function getServicesToExtend(): array
360
    {
361 37
        return (array)$this->servicesToExtend;
362
    }
363
364 77
    public function setFileCacheEnabled(?bool $flag): self
365
    {
366 77
        $this->markPropertyChanged(self::fileCacheEnabled, $flag);
367 77
        $this->fileCacheEnabled = $flag ?? self::DEFAULT_FILE_CACHE_ENABLED;
368
369 77
        return $this;
370
    }
371
372 59
    public function canCreateEventDispatcher(): bool
373
    {
374 59
        return $this->areEventListenersEnabled
375 59
            && $this->hasEventListeners();
376
    }
377
378
    /**
379
     * @param ?array<string,mixed> $configKeyValues
380
     */
381 77
    public function setConfigKeyValues(?array $configKeyValues): self
382
    {
383 77
        $this->markPropertyChanged(self::configKeyValues, $configKeyValues);
384 77
        $this->configKeyValues = $configKeyValues ?? self::DEFAULT_CONFIG_KEY_VALUES;
385
386 77
        return $this;
387
    }
388
389
    /**
390
     * @return array<class-string,list<callable>>|null
391
     */
392 3
    public function getSpecificListeners(): ?array
393
    {
394 3
        return $this->specificListeners;
395
    }
396
397
    /**
398
     * @return list<callable>|null
399
     */
400 3
    public function getGenericListeners(): ?array
401
    {
402 3
        return $this->genericListeners;
403
    }
404
405 27
    public function isPropertyChanged(string $name): bool
406
    {
407 27
        return $this->changedProperties[$name] ?? false;
408
    }
409
410 27
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): self
411
    {
412 27
        $this->eventDispatcher = $eventDispatcher;
413
414 27
        return $this;
415
    }
416
417 27
    public function combine(self $other): self
418
    {
419 27
        return (new SetupCombinator($this))->combine($other);
420
    }
421
422
    /**
423
     * @param list<Closure> $servicesToExtend
424
     */
425 1
    public function addServicesToExtend(string $serviceId, array $servicesToExtend): self
426
    {
427 1
        $this->servicesToExtend[$serviceId] ??= [];
428 1
        $this->servicesToExtend[$serviceId] = array_merge(
429 1
            $this->servicesToExtend[$serviceId],
430 1
            $servicesToExtend,
431 1
        );
432
433 1
        return $this;
434
    }
435
436 27
    public function combineExternalServices(array $list): void
437
    {
438 27
        $this->setExternalServices(array_merge($this->externalServices ?? [], $list));
439
    }
440
441 1
    public function combineProjectNamespaces(array $list): void
442
    {
443 1
        $this->setProjectNamespaces(array_merge($this->projectNamespaces ?? [], $list));
444
    }
445
446 1
    public function combineConfigKeyValues(array $list): void
447
    {
448 1
        $this->setConfigKeyValues(array_merge($this->configKeyValues ?? [], $list));
449
    }
450
451
    /**
452
     * @param list<class-string<PluginInterface>> $list
453
     */
454 1
    public function combinePlugins(array $list): void
455
    {
456 1
        $this->setAfterPlugins(array_merge($this->afterPlugins ?? [], $list));
457
    }
458
459
    /**
460
     * @return list<class-string<PluginInterface>>
461
     */
462 86
    public function getAfterPlugins(): array
463
    {
464 86
        return (array)$this->afterPlugins;
465
    }
466
467 77
    private function setAreEventListenersEnabled(?bool $flag): self
468
    {
469 77
        $this->areEventListenersEnabled = $flag ?? self::DEFAULT_ARE_EVENT_LISTENERS_ENABLED;
470
471 77
        return $this;
472
    }
473
474 58
    private function hasEventListeners(): bool
475
    {
476 58
        return !empty($this->genericListeners)
477 58
            || !empty($this->specificListeners);
478
    }
479
480
    /**
481
     * @param ?list<callable> $listeners
482
     */
483 77
    private function setGenericListeners(?array $listeners): self
484
    {
485 77
        $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...
486
487 77
        return $this;
488
    }
489
490
    /**
491
     * @param ?array<string,list<Closure>> $list
492
     */
493 77
    private function setServicesToExtend(?array $list): self
494
    {
495 77
        $this->markPropertyChanged(self::servicesToExtend, $list);
496 77
        $this->servicesToExtend = $list ?? self::DEFAULT_SERVICES_TO_EXTEND;
497
498 77
        return $this;
499
    }
500
501
    /**
502
     * @param ?list<class-string<PluginInterface>> $list
503
     */
504 77
    private function setAfterPlugins(?array $list): self
505
    {
506 77
        $this->markPropertyChanged(self::afterPlugins, $list);
507 77
        $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...
508
509 77
        return $this;
510
    }
511
512
    /**
513
     * @param ?array<class-string,list<callable>> $listeners
514
     */
515 77
    private function setSpecificListeners(?array $listeners): self
516
    {
517 77
        $this->specificListeners = $listeners ?? self::DEFAULT_SPECIFIC_LISTENERS;
518
519 77
        return $this;
520
    }
521
522 80
    private function markPropertyChanged(string $name, mixed $value): void
523
    {
524 80
        $this->changedProperties[$name] = ($value !== null);
525
    }
526
}
527