Test Failed
Push — rename-plugins-to-after-plugin... ( 2b0a51 )
by Chema
02:52
created

SetupGacela::setBeforePlugins()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
ccs 0
cts 0
cp 0
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 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
17
use RuntimeException;
18
19
use function is_callable;
20
21
/**
22
 * @psalm-suppress ArgumentTypeCoercion,MixedArgumentTypeCoercion
23
 */
24
final class SetupGacela extends AbstractSetupGacela
25
{
26
    public const shouldResetInMemoryCache = 'shouldResetInMemoryCache';
27
    public const fileCacheEnabled = 'fileCacheEnabled';
28
    public const fileCacheDirectory = 'fileCacheDirectory';
29
    public const externalServices = 'externalServices';
30
    public const projectNamespaces = 'projectNamespaces';
31
    public const configKeyValues = 'configKeyValues';
32
    public const servicesToExtend = 'servicesToExtend';
33
    public const afterPlugins = 'afterPlugins';
34
    public const beforePlugins = 'beforePlugins';
35
36
    private const DEFAULT_ARE_EVENT_LISTENERS_ENABLED = true;
37
    private const DEFAULT_SHOULD_RESET_IN_MEMORY_CACHE = false;
38
    private const DEFAULT_FILE_CACHE_ENABLED = GacelaFileCache::DEFAULT_ENABLED_VALUE;
39
    private const DEFAULT_FILE_CACHE_DIRECTORY = GacelaFileCache::DEFAULT_DIRECTORY_VALUE;
40
    private const DEFAULT_PROJECT_NAMESPACES = [];
41
    private const DEFAULT_CONFIG_KEY_VALUES = [];
42
    private const DEFAULT_GENERIC_LISTENERS = [];
43
    private const DEFAULT_SPECIFIC_LISTENERS = [];
44
    private const DEFAULT_SERVICES_TO_EXTEND = [];
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 111
    private ?array $beforePlugins = null;
95
96 111
    /** @var ?list<class-string> */
97 60
    private ?array $afterPlugins = null;
98
99 111
    public function __construct()
100 111
    {
101 111
        $emptyFn = static function (): void {
102
        };
103
104
        $this->configFn = $emptyFn;
105
        $this->bindingsFn = $emptyFn;
106
        $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 59
    }
123
124 59
    /**
125 59
     * @param callable(GacelaConfig):void $setupGacelaFileFn
126
     */
127 59
    public static function fromCallable(callable $setupGacelaFileFn): self
128
    {
129
        $gacelaConfig = new GacelaConfig();
130 77
        $setupGacelaFileFn($gacelaConfig);
131
        self::runBeforePlugins($gacelaConfig);
132 77
133
        return self::fromGacelaConfig($gacelaConfig);
134 77
    }
135 77
136 77
    public static function fromGacelaConfig(GacelaConfig $gacelaConfig): self
137 77
    {
138 77
        $build = $gacelaConfig->build();
139 77
140 77
        return (new self())
141 77
            ->setExternalServices($build['external-services'])
142 77
            ->setConfigBuilder($build['config-builder'])
143 77
            ->setSuffixTypesBuilder($build['suffix-types-builder'])
144 77
            ->setBindingsBuilder($build['mapping-interfaces-builder'])
145 77
            ->setShouldResetInMemoryCache($build['should-reset-in-memory-cache'])
146 77
            ->setFileCacheEnabled($build['file-cache-enabled'])
147 77
            ->setFileCacheDirectory($build['file-cache-directory'])
148 77
            ->setProjectNamespaces($build['project-namespaces'])
149
            ->setConfigKeyValues($build['config-key-values'])
150
            ->setAreEventListenersEnabled($build['are-event-listeners-enabled'])
151
            ->setGenericListeners($build['generic-listeners'])
152
            ->setSpecificListeners($build['specific-listeners'])
153
            ->setBeforePlugins($build['before-plugins'])
154 80
            ->setAfterPlugins($build['after-plugins'])
155
            ->setServicesToExtend($build['services-to-extend']);
156 80
    }
157 80
158
    /**
159 80
     * @param array<string,class-string|object|callable> $array
160
     */
161
    public function setExternalServices(array $array): self
162 77
    {
163
        $this->markPropertyChanged(self::externalServices, true);
164 77
        $this->externalServices = $array;
165
166 77
        return $this;
167
    }
168
169 77
    public function setConfigBuilder(ConfigBuilder $builder): self
170
    {
171 77
        $this->configBuilder = $builder;
172
173 77
        return $this;
174
    }
175
176 77
    public function setSuffixTypesBuilder(SuffixTypesBuilder $builder): self
177
    {
178 77
        $this->suffixTypesBuilder = $builder;
179
180 77
        return $this;
181
    }
182
183
    public function setBindingsBuilder(BindingsBuilder $builder): self
184
    {
185
        $this->bindingsBuilder = $builder;
186 3
187
        return $this;
188 3
    }
189
190 3
    /**
191
     * @param callable(ConfigBuilder):void $callable
192
     */
193 61
    public function setConfigFn(callable $callable): self
194
    {
195 61
        $this->configFn = $callable;
196
197 61
        return $this;
198 53
    }
199
200
    public function buildConfig(ConfigBuilder $builder): ConfigBuilder
201 61
    {
202
        $builder = parent::buildConfig($builder);
203 61
204
        if ($this->configBuilder) {
205
            $builder = $this->configBuilder;
206
        }
207
208
        ($this->configFn)($builder);
209 3
210
        return $builder;
211 3
    }
212
213 3
    /**
214
     * @param callable(BindingsBuilder,array<string,mixed>):void $callable
215
     */
216
    public function setBindingsFn(callable $callable): self
217
    {
218
        $this->bindingsFn = $callable;
219
220
        return $this;
221 61
    }
222
223
    /**
224
     * Define the mapping between interfaces and concretions, so Gacela services will auto-resolve them automatically.
225 61
     *
226
     * @param array<string,class-string|object|callable> $externalServices
227 61
     */
228 53
    public function buildBindings(
229
        BindingsBuilder $builder,
230
        array $externalServices,
231 61
    ): BindingsBuilder {
232 61
        $builder = parent::buildBindings($builder, $externalServices);
233 61
234 61
        if ($this->bindingsBuilder) {
235
            $builder = $this->bindingsBuilder;
236 61
        }
237
238
        ($this->bindingsFn)(
239
            $builder,
240
            array_merge($this->externalServices ?? [], $externalServices)
241
        );
242 3
243
        return $builder;
244 3
    }
245
246 3
    /**
247
     * @param callable(SuffixTypesBuilder):void $callable
248
     */
249
    public function setSuffixTypesFn(callable $callable): self
250
    {
251
        $this->suffixTypesFn = $callable;
252 61
253
        return $this;
254 61
    }
255
256 61
    /**
257 53
     * Allow overriding gacela resolvable types.
258
     */
259
    public function buildSuffixTypes(SuffixTypesBuilder $builder): SuffixTypesBuilder
260 61
    {
261
        $builder = parent::buildSuffixTypes($builder);
262 61
263
        if ($this->suffixTypesBuilder) {
264
            $builder = $this->suffixTypesBuilder;
265
        }
266
267
        ($this->suffixTypesFn)($builder);
268 27
269
        return $builder;
270 27
    }
271 27
272 27
    /**
273 27
     * @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
    public function externalServices(): array
276 77
    {
277
        return array_merge(
278 77
            parent::externalServices(),
279 77
            $this->externalServices ?? [],
280
        );
281 77
    }
282
283
    public function setShouldResetInMemoryCache(?bool $flag): self
284 86
    {
285
        $this->markPropertyChanged(self::shouldResetInMemoryCache, $flag);
286 86
        $this->shouldResetInMemoryCache = $flag ?? self::DEFAULT_SHOULD_RESET_IN_MEMORY_CACHE;
287
288
        return $this;
289 40
    }
290
291 40
    public function shouldResetInMemoryCache(): bool
292
    {
293
        return (bool)$this->shouldResetInMemoryCache;
294 6
    }
295
296 6
    public function isFileCacheEnabled(): bool
297
    {
298
        return (bool)$this->fileCacheEnabled;
299 77
    }
300
301 77
    public function getFileCacheDirectory(): string
302 77
    {
303
        return (string)$this->fileCacheDirectory;
304 77
    }
305
306
    public function setFileCacheDirectory(?string $dir): self
307
    {
308
        $this->markPropertyChanged(self::fileCacheDirectory, $dir);
309
        $this->fileCacheDirectory = $dir ?? self::DEFAULT_FILE_CACHE_DIRECTORY;
310 77
311
        return $this;
312 77
    }
313 77
314
    /**
315 77
     * @param ?list<string> $list
316
     */
317
    public function setProjectNamespaces(?array $list): self
318
    {
319
        $this->markPropertyChanged(self::projectNamespaces, $list);
320
        $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 39
322
        return $this;
323 39
    }
324
325
    /**
326
     * @return list<string>
327
     */
328
    public function getProjectNamespaces(): array
329 86
    {
330
        return (array)$this->projectNamespaces;
331 86
    }
332
333
    /**
334 49
     * @return array<string,mixed>
335
     */
336 49
    public function getConfigKeyValues(): array
337 18
    {
338
        return (array)$this->configKeyValues;
339
    }
340 35
341 11
    public function getEventDispatcher(): EventDispatcherInterface
342 11
    {
343
        if ($this->eventDispatcher !== null) {
344 11
            return $this->eventDispatcher;
345 5
        }
346 5
347
        if ($this->canCreateEventDispatcher()) {
348
            $this->eventDispatcher = new ConfigurableEventDispatcher();
349
            $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 24
351
            foreach ($this->specificListeners ?? [] as $event => $listeners) {
352
                foreach ($listeners as $callable) {
353 35
                    $this->eventDispatcher->registerSpecificListener($event, $callable);
354
                }
355
            }
356
        } else {
357
            $this->eventDispatcher = new NullEventDispatcher();
358
        }
359 37
360
        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 37
    }
362
363
    /**
364 77
     * @return array<string,list<Closure>>
365
     */
366 77
    public function getServicesToExtend(): array
367 77
    {
368
        return (array)$this->servicesToExtend;
369 77
    }
370
371
    public function setFileCacheEnabled(?bool $flag): self
372 59
    {
373
        $this->markPropertyChanged(self::fileCacheEnabled, $flag);
374 59
        $this->fileCacheEnabled = $flag ?? self::DEFAULT_FILE_CACHE_ENABLED;
375 59
376
        return $this;
377
    }
378
379
    public function canCreateEventDispatcher(): bool
380
    {
381 77
        return $this->areEventListenersEnabled
382
            && $this->hasEventListeners();
383 77
    }
384 77
385
    /**
386 77
     * @param ?array<string,mixed> $configKeyValues
387
     */
388
    public function setConfigKeyValues(?array $configKeyValues): self
389
    {
390
        $this->markPropertyChanged(self::configKeyValues, $configKeyValues);
391
        $this->configKeyValues = $configKeyValues ?? self::DEFAULT_CONFIG_KEY_VALUES;
392 3
393
        return $this;
394 3
    }
395
396
    /**
397
     * @return array<class-string,list<callable>>|null
398
     */
399
    public function getSpecificListeners(): ?array
400 3
    {
401
        return $this->specificListeners;
402 3
    }
403
404
    /**
405 27
     * @return list<callable>|null
406
     */
407 27
    public function getGenericListeners(): ?array
408
    {
409
        return $this->genericListeners;
410 27
    }
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
        return $this;
422
    }
423
424
    public function combine(self $other): self
425 1
    {
426
        return (new SetupCombinator($this))->combine($other);
427 1
    }
428 1
429 1
    /**
430 1
     * @param list<Closure> $servicesToExtend
431 1
     */
432
    public function addServicesToExtend(string $serviceId, array $servicesToExtend): self
433 1
    {
434
        $this->servicesToExtend[$serviceId] ??= [];
435
        $this->servicesToExtend[$serviceId] = array_merge(
436 27
            $this->servicesToExtend[$serviceId],
437
            $servicesToExtend,
438 27
        );
439
440
        return $this;
441 1
    }
442
443 1
    public function combineExternalServices(array $list): void
444
    {
445
        $this->setExternalServices(array_merge($this->externalServices ?? [], $list));
446 1
    }
447
448 1
    public function combineProjectNamespaces(array $list): void
449
    {
450
        $this->setProjectNamespaces(array_merge($this->projectNamespaces ?? [], $list));
451
    }
452
453
    public function combineConfigKeyValues(array $list): void
454 1
    {
455
        $this->setConfigKeyValues(array_merge($this->configKeyValues ?? [], $list));
456 1
    }
457
458
    /**
459
     * @param list<class-string> $list
460
     */
461
    public function combineBeforePlugins(array $list): void
462 86
    {
463
        $this->setBeforePlugins(array_merge($this->beforePlugins ?? [], $list));
464 86
    }
465
466
    /**
467 77
     * @param list<class-string> $list
468
     */
469 77
    public function combineAfterPlugins(array $list): void
470
    {
471 77
        $this->setAfterPlugins(array_merge($this->afterPlugins ?? [], $list));
472
    }
473
474 58
    /**
475
     * @return list<class-string>
476 58
     */
477 58
    public function getBeforePlugins(): array
478
    {
479
        return (array)$this->beforePlugins;
480
    }
481
482
    /**
483 77
     * @return list<class-string>
484
     */
485 77
    public function getAfterPlugins(): array
486
    {
487 77
        return (array)$this->afterPlugins;
488
    }
489
490
    private static function runBeforePlugins(GacelaConfig $config): void
491
    {
492
        $plugins = $config->build()['before-plugins'] ?? [];
493 77
494
        if ($plugins === []) {
495 77
            return;
496 77
        }
497
498 77
        foreach ($plugins as $pluginName) {
499
            /** @var callable $plugin */
500
            $plugin = Container::create($pluginName);
501
            $plugin($config);
502
        }
503
    }
504 77
505
    private function setAreEventListenersEnabled(?bool $flag): self
506 77
    {
507 77
        $this->areEventListenersEnabled = $flag ?? self::DEFAULT_ARE_EVENT_LISTENERS_ENABLED;
508
509 77
        return $this;
510
    }
511
512
    private function hasEventListeners(): bool
513
    {
514
        return !empty($this->genericListeners)
515 77
            || !empty($this->specificListeners);
516
    }
517 77
518
    /**
519 77
     * @param ?list<callable> $listeners
520
     */
521
    private function setGenericListeners(?array $listeners): self
522 80
    {
523
        $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...
524 80
525
        return $this;
526
    }
527
528
    /**
529
     * @param ?array<string,list<Closure>> $list
530
     */
531
    private function setServicesToExtend(?array $list): self
532
    {
533
        $this->markPropertyChanged(self::servicesToExtend, $list);
534
        $this->servicesToExtend = $list ?? self::DEFAULT_SERVICES_TO_EXTEND;
535
536
        return $this;
537
    }
538
539
    /**
540
     * @param ?list<class-string> $list
541
     */
542
    private function setBeforePlugins(?array $list): self
543
    {
544
        $this->markPropertyChanged(self::beforePlugins, $list);
545
        $this->beforePlugins = $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 $beforePlugins.

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...
546
547
        return $this;
548
    }
549
550
    /**
551
     * @param ?list<class-string> $list
552
     */
553
    private function setAfterPlugins(?array $list): self
554
    {
555
        $this->markPropertyChanged(self::afterPlugins, $list);
556
        $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...
557
558
        return $this;
559
    }
560
561
    /**
562
     * @param ?array<class-string,list<callable>> $listeners
563
     */
564
    private function setSpecificListeners(?array $listeners): self
565
    {
566
        $this->specificListeners = $listeners ?? self::DEFAULT_SPECIFIC_LISTENERS;
567
568
        return $this;
569
    }
570
571
    private function markPropertyChanged(string $name, mixed $value): void
572
    {
573
        $this->changedProperties[$name] = ($value !== null);
574
    }
575
}
576