SetupGacela::getContextualBindings()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 1
cts 1
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework\Bootstrap;
6
7
use Closure;
8
use Gacela\Framework\Bootstrap\Setup\BuilderExecutor;
9
use Gacela\Framework\Bootstrap\Setup\GacelaConfigExtender;
10
use Gacela\Framework\Bootstrap\Setup\Properties;
11
use Gacela\Framework\Bootstrap\Setup\PropertyChangeTracker;
12
use Gacela\Framework\Bootstrap\Setup\PropertyMerger;
13
use Gacela\Framework\Bootstrap\Setup\SetupInitializer;
14
use Gacela\Framework\Bootstrap\Setup\SetupMerger;
15
use Gacela\Framework\Config\GacelaConfigBuilder\AppConfigBuilder;
16
use Gacela\Framework\Config\GacelaConfigBuilder\BindingsBuilder;
17
use Gacela\Framework\Config\GacelaConfigBuilder\SuffixTypesBuilder;
18
use Gacela\Framework\Event\Dispatcher\EventDispatcherInterface;
19
use RuntimeException;
20
21
use function is_callable;
22
use function sprintf;
23
24
/**
25
 * @psalm-suppress ArgumentTypeCoercion,MixedArgumentTypeCoercion
26
 */
27
final class SetupGacela extends AbstractSetupGacela
28
{
29
    private readonly Properties $properties;
30
31
    private readonly PropertyChangeTracker $changeTracker;
32
33
    private readonly BuilderExecutor $builderExecutor;
34
35
    private readonly PropertyMerger $propertyMerger;
36
37
    public function __construct()
38
    {
39
        $this->properties = new Properties();
40
        $this->changeTracker = new PropertyChangeTracker();
41
        $this->builderExecutor = new BuilderExecutor($this->properties);
42
        $this->propertyMerger = new PropertyMerger($this);
43
    }
44
45
    /**
46
     * @codeCoverageIgnore
47
     */
48
    public static function fromFile(string $gacelaFilePath): self
49
    {
50
        if (!is_file($gacelaFilePath)) {
51
            throw new RuntimeException(sprintf("Invalid file path: '%s'", $gacelaFilePath));
52
        }
53
54
        /** @var callable(GacelaConfig):void|null $setupGacelaFileFn */
55
        $setupGacelaFileFn = include $gacelaFilePath;
56
        if (!is_callable($setupGacelaFileFn)) {
57
            return new self();
58
        }
59
60
        return self::fromCallable($setupGacelaFileFn);
61
    }
62
63
    /**
64
     * @param callable(GacelaConfig):void $setupGacelaFileFn
65
     */
66
    public static function fromCallable(callable $setupGacelaFileFn): self
67
    {
68
        $gacelaConfig = new GacelaConfig();
69
        $setupGacelaFileFn($gacelaConfig);
70
71
        return self::fromGacelaConfig($gacelaConfig);
72
    }
73
74
    public static function fromGacelaConfig(GacelaConfig $gacelaConfig): self
75 133
    {
76
        (new GacelaConfigExtender())->extend($gacelaConfig);
77 133
78 74
        $dto = $gacelaConfig->toTransfer();
79
        $setup = new self();
80 133
81 133
        return (new SetupInitializer($setup))->initializeFromTransfer($dto);
82 133
    }
83
84
    /**
85
     * @param array<string,class-string|object|callable> $array
86
     */
87
    public function setExternalServices(?array $array): self
88
    {
89
        $this->markPropertyAsChanged(self::externalServices, $array !== null);
90
        $this->properties->externalServices = $array; // No default fallback for external services
91
92
        return $this;
93
    }
94
95
    public function setAppConfigBuilder(AppConfigBuilder $builder): self
96
    {
97
        $this->properties->appConfigBuilder = $builder;
98
99
        return $this;
100
    }
101
102
    public function setSuffixTypesBuilder(SuffixTypesBuilder $builder): self
103
    {
104
        $this->properties->suffixTypesBuilder = $builder;
105
106 74
        return $this;
107
    }
108 74
109 74
    public function setBindingsBuilder(BindingsBuilder $builder): self
110
    {
111 74
        $this->properties->bindingsBuilder = $builder;
112
113
        return $this;
114 93
    }
115
116 93
    /**
117
     * @param callable(AppConfigBuilder):void $callable
118 93
     */
119
    public function setAppConfigFn(callable $callable): self
120 93
    {
121 93
        $this->properties->appConfigFn = $callable;
122 93
123 93
        return $this;
124 93
    }
125 93
126 93
    public function buildAppConfig(AppConfigBuilder $builder): AppConfigBuilder
127 93
    {
128 93
        $builder = parent::buildAppConfig($builder);
129 93
130 93
        return $this->builderExecutor->buildAppConfig($builder);
131 93
    }
132 93
133 93
    /**
134 93
     * @param callable(BindingsBuilder,array<string,mixed>):void $callable
135 93
     */
136
    public function setBindingsFn(callable $callable): self
137
    {
138
        $this->properties->bindingsFn = $callable;
139
140
        return $this;
141 96
    }
142
143 96
    /**
144 96
     * Define the mapping between interfaces and concretions, so Gacela services will auto-resolve them automatically.
145
     *
146 96
     * @param array<string,class-string|object|callable> $externalServices
147
     */
148
    public function buildBindings(
149 93
        BindingsBuilder $builder,
150
        array $externalServices,
151 93
    ): BindingsBuilder {
152
        $builder = parent::buildBindings($builder, $externalServices);
153 93
154
        return $this->builderExecutor->buildBindings($builder, $externalServices);
155
    }
156 93
157
    /**
158 93
     * @param callable(SuffixTypesBuilder):void $callable
159
     */
160 93
    public function setSuffixTypesFn(callable $callable): self
161
    {
162
        $this->properties->suffixTypesFn = $callable;
163 93
164
        return $this;
165 93
    }
166
167 93
    /**
168
     * Allow overriding gacela resolvable types.
169
     */
170
    public function buildSuffixTypes(SuffixTypesBuilder $builder): SuffixTypesBuilder
171
    {
172
        $builder = parent::buildSuffixTypes($builder);
173 3
174
        return $this->builderExecutor->buildSuffixTypes($builder);
175 3
    }
176
177 3
    /**
178
     * @return array<string, class-string|object|callable>
179
     */
180 75
    public function externalServices(): array
181
    {
182 75
        return array_merge(
183
            parent::externalServices(),
184 75
            $this->properties->externalServices ?? [],
185 67
        );
186
    }
187
188 75
    public function setShouldResetInMemoryCache(?bool $flag): self
189
    {
190 75
        $this->properties->shouldResetInMemoryCache = $this->setPropertyWithTracking(
191
            self::shouldResetInMemoryCache,
192
            $flag,
193
            self::DEFAULT_SHOULD_RESET_IN_MEMORY_CACHE,
194
        );
195
196 3
        return $this;
197
    }
198 3
199
    public function shouldResetInMemoryCache(): bool
200 3
    {
201
        return $this->properties->shouldResetInMemoryCache ?? self::DEFAULT_SHOULD_RESET_IN_MEMORY_CACHE;
202
    }
203
204
    public function isFileCacheEnabled(): bool
205
    {
206
        return $this->properties->fileCacheEnabled ?? self::DEFAULT_FILE_CACHE_ENABLED;
207
    }
208 75
209
    public function getFileCacheDirectory(): string
210
    {
211
        return $this->properties->fileCacheDirectory ?? '';
212 75
    }
213
214 75
    public function setFileCacheDirectory(?string $dir): self
215 67
    {
216
        $this->properties->fileCacheDirectory = $this->setPropertyWithTracking(
217
            self::fileCacheDirectory,
218 75
            $dir,
219 75
            self::DEFAULT_FILE_CACHE_DIRECTORY,
220 75
        );
221 75
222
        return $this;
223 75
    }
224
225
    /**
226
     * @param ?list<string> $list
227
     */
228
    public function setProjectNamespaces(?array $list): self
229 3
    {
230
        $this->properties->projectNamespaces = $this->setPropertyWithTracking(
231 3
            self::projectNamespaces,
232
            $list,
233 3
            self::DEFAULT_PROJECT_NAMESPACES,
234
        );
235
236
        return $this;
237
    }
238
239 75
    /**
240
     * @return list<string>
241 75
     */
242
    public function getProjectNamespaces(): array
243 75
    {
244 67
        return $this->properties->projectNamespaces ?? self::DEFAULT_PROJECT_NAMESPACES;
245
    }
246
247 75
    /**
248
     * @return array<string,mixed>
249 75
     */
250
    public function getConfigKeyValues(): array
251
    {
252
        return $this->properties->configKeyValues ?? self::DEFAULT_CONFIG_KEY_VALUES;
253
    }
254
255 29
    public function getEventDispatcher(): EventDispatcherInterface
256
    {
257 29
        return $this->properties->eventDispatcher ??= SetupEventDispatcher::getDispatcher($this);
258 29
    }
259 29
260 29
    /**
261
     * @return array<string,list<Closure>>
262
     */
263 93
    public function getServicesToExtend(): array
264
    {
265 93
        return $this->properties->servicesToExtend ?? self::DEFAULT_SERVICES_TO_EXTEND;
266 93
    }
267
268 93
    /**
269
     * @return array<string,Closure>
270
     */
271 107
    public function getFactories(): array
272
    {
273 107
        return $this->properties->factories ?? self::DEFAULT_FACTORIES;
274
    }
275
276 49
    /**
277
     * @return array<string,Closure>
278 49
     */
279
    public function getProtectedServices(): array
280
    {
281 7
        return $this->properties->protectedServices ?? self::DEFAULT_PROTECTED_SERVICES;
282
    }
283 7
284
    /**
285
     * @return array<string,string>
286 93
     */
287
    public function getAliases(): array
288 93
    {
289 93
        return $this->properties->aliases ?? self::DEFAULT_ALIASES;
290
    }
291 93
292
    /**
293
     * @return array<string,array<class-string,class-string|callable|object>>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string,array<class...tring|callable|object>> at position 6 could not be parsed: Unknown type name 'class-string' at position 6 in array<string,array<class-string,class-string|callable|object>>.
Loading history...
294
     */
295
    public function getContextualBindings(): array
296
    {
297 93
        return $this->properties->contextualBindings ?? self::DEFAULT_CONTEXTUAL_BINDINGS;
298
    }
299 93
300 93
    public function setFileCacheEnabled(?bool $flag): self
301
    {
302 93
        $this->properties->fileCacheEnabled = $this->setPropertyWithTracking(
303
            self::fileCacheEnabled,
304
            $flag,
305
            self::DEFAULT_FILE_CACHE_ENABLED,
306
        );
307
308 45
        return $this;
309
    }
310 45
311
    public function canCreateEventDispatcher(): bool
312
    {
313
        return $this->properties->areEventListenersEnabled === true
314
            && $this->hasEventListeners();
315
    }
316 107
317
    /**
318 107
     * @param ?array<string,mixed> $configKeyValues
319
     */
320
    public function setConfigKeyValues(?array $configKeyValues): self
321 70
    {
322
        $this->properties->configKeyValues = $this->setPropertyWithTracking(
323 70
            self::configKeyValues,
324
            $configKeyValues,
325
            self::DEFAULT_CONFIG_KEY_VALUES,
326
        );
327
328
        return $this;
329 120
    }
330
331 120
    /**
332
     * @return array<class-string,list<callable>>|null
333
     */
334 93
    public function getSpecificListeners(): ?array
335
    {
336 93
        return $this->properties->specificListeners;
337 93
    }
338
339 93
    /**
340
     * @return list<callable>|null
341
     */
342 70
    public function getGenericListeners(): ?array
343
    {
344 70
        return $this->properties->genericListeners;
345 70
    }
346
347
    public function isPropertyChanged(string $name): bool
348
    {
349
        return $this->changeTracker->isChanged($name);
350
    }
351 93
352
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): self
353 93
    {
354 93
        $this->properties->eventDispatcher = $eventDispatcher;
355
356 93
        return $this;
357
    }
358
359
    public function merge(self $other): self
360
    {
361
        return (new SetupMerger($this))->merge($other);
362 16
    }
363
364 16
    /**
365
     * @param list<Closure> $servicesToExtend
366
     */
367
    public function addServicesToExtend(string $serviceId, array $servicesToExtend): self
368
    {
369
        $this->properties->servicesToExtend[$serviceId] ??= [];
370 16
        $this->properties->servicesToExtend[$serviceId] = [...$this->properties->servicesToExtend[$serviceId], ...$servicesToExtend];
371
372 16
        return $this;
373
    }
374
375 29
    /**
376
     * @param array<string,class-string|object|callable> $list
377 29
     */
378
    public function mergeExternalServices(array $list): void
379
    {
380 29
        $this->propertyMerger->mergeExternalServices($list);
381
    }
382 29
383
    /**
384 29
     * @param list<string> $list
385
     */
386
    public function mergeProjectNamespaces(array $list): void
387 29
    {
388
        $this->propertyMerger->mergeProjectNamespaces($list);
389 29
    }
390
391
    /**
392
     * @param array<string,mixed> $list
393
     */
394
    public function mergeConfigKeyValues(array $list): void
395 1
    {
396
        $this->propertyMerger->mergeConfigKeyValues($list);
397 1
    }
398 1
399 1
    /**
400 1
     * @param list<class-string> $list
401 1
     */
402
    public function mergeGacelaConfigsToExtend(array $list): void
403 1
    {
404
        $this->propertyMerger->mergeGacelaConfigsToExtend($list);
405
    }
406 29
407
    /**
408 29
     * @param list<class-string|callable> $list
409
     */
410
    public function mergePlugins(array $list): void
411 1
    {
412
        $this->propertyMerger->mergePlugins($list);
413 1
    }
414
415
    /**
416 1
     * @param array<string,Closure> $list
417
     */
418 1
    public function mergeFactories(array $list): void
419
    {
420
        $this->propertyMerger->mergeFactories($list);
421
    }
422
423
    /**
424 1
     * @param array<string,Closure> $list
425
     */
426 1
    public function mergeProtectedServices(array $list): void
427 1
    {
428 1
        $this->propertyMerger->mergeProtectedServices($list);
429
    }
430
431
    /**
432
     * @param array<string,string> $list
433
     */
434 1
    public function mergeAliases(array $list): void
435
    {
436 1
        $this->propertyMerger->mergeAliases($list);
437
    }
438
439
    /**
440
     * @param array<string,array<class-string,class-string|callable|object>> $list
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string,array<class...tring|callable|object>> at position 6 could not be parsed: Unknown type name 'class-string' at position 6 in array<string,array<class-string,class-string|callable|object>>.
Loading history...
441
     */
442 1
    public function mergeContextualBindings(array $list): void
443
    {
444 1
        $this->propertyMerger->mergeContextualBindings($list);
445
    }
446
447
    /**
448
     * @return list<class-string>
449
     */
450 107
    public function getGacelaConfigsToExtend(): array
451
    {
452 107
        return $this->properties->gacelaConfigsToExtend ?? self::DEFAULT_GACELA_CONFIGS_TO_EXTEND;
453
    }
454
455 93
    /**
456
     * @return list<class-string|callable>
457 93
     */
458
    public function getPlugins(): array
459 93
    {
460
        return $this->properties->plugins ?? self::DEFAULT_PLUGINS;
461
    }
462 70
463
    /**
464 70
     * @internal Used by PropertyMerger - do not call directly
465 70
     *
466
     * @param ?list<class-string> $list
467
     */
468
    public function setGacelaConfigsToExtend(?array $list): self
469
    {
470
        $this->properties->gacelaConfigsToExtend = $this->setPropertyWithTracking(
471 93
            self::gacelaConfigsToExtend,
472
            $list,
473 93
            self::DEFAULT_GACELA_CONFIGS_TO_EXTEND,
474
        );
475 93
476
        return $this;
477
    }
478
479
    /**
480
     * @internal Used by PropertyMerger - do not call directly
481 93
     *
482
     * @param ?list<class-string|callable> $list
483 93
     */
484 93
    public function setPlugins(?array $list): self
485
    {
486 93
        $this->properties->plugins = $this->setPropertyWithTracking(
487
            self::plugins,
488
            $list,
489
            self::DEFAULT_PLUGINS,
490
        );
491
492 93
        return $this;
493
    }
494 93
495 93
    /**
496
     * @internal Used by SetupInitializer - do not call directly
497 93
     */
498
    public function setAreEventListenersEnabled(?bool $flag): self
499
    {
500
        $this->properties->areEventListenersEnabled = $flag ?? self::DEFAULT_ARE_EVENT_LISTENERS_ENABLED;
501
502
        return $this;
503 93
    }
504
505 93
    /**
506 93
     * @internal Used by SetupInitializer - do not call directly
507
     *
508 93
     * @param ?list<callable> $listeners
509
     */
510
    public function setGenericListeners(?array $listeners): self
511
    {
512
        $this->properties->genericListeners = $listeners ?? self::DEFAULT_GENERIC_LISTENERS;
0 ignored issues
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\Setup\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...
513
514 93
        return $this;
515
    }
516 93
517
    /**
518 93
     * @internal Used by SetupInitializer - do not call directly
519
     *
520
     * @param ?array<string,list<Closure>> $list
521 96
     */
522
    public function setServicesToExtend(?array $list): self
523 96
    {
524
        $this->properties->servicesToExtend = $this->setPropertyWithTracking(
525
            self::servicesToExtend,
526
            $list,
527
            self::DEFAULT_SERVICES_TO_EXTEND,
528
        );
529
530
        return $this;
531
    }
532
533
    /**
534
     * @internal Used by SetupInitializer - do not call directly
535
     *
536
     * @param ?array<string,Closure> $list
537
     */
538
    public function setFactories(?array $list): self
539
    {
540
        $this->properties->factories = $this->setPropertyWithTracking(
541
            self::factories,
542
            $list,
543
            self::DEFAULT_FACTORIES,
544
        );
545
546
        return $this;
547
    }
548
549
    /**
550
     * @internal Used by SetupInitializer - do not call directly
551
     *
552
     * @param ?array<string,Closure> $list
553
     */
554
    public function setProtectedServices(?array $list): self
555
    {
556
        $this->properties->protectedServices = $this->setPropertyWithTracking(
557
            self::protectedServices,
558
            $list,
559
            self::DEFAULT_PROTECTED_SERVICES,
560
        );
561
562
        return $this;
563
    }
564
565
    /**
566
     * @internal Used by SetupInitializer - do not call directly
567
     *
568
     * @param ?array<string,string> $list
569
     */
570
    public function setAliases(?array $list): self
571
    {
572
        $this->properties->aliases = $this->setPropertyWithTracking(
573
            self::aliases,
574
            $list,
575
            self::DEFAULT_ALIASES,
576
        );
577
578
        return $this;
579
    }
580
581
    /**
582
     * @internal Used by SetupInitializer - do not call directly
583
     *
584
     * @param ?array<string,array<class-string,class-string|callable|object>> $list
0 ignored issues
show
Documentation Bug introduced by
The doc comment ?array<string,array<clas...tring|callable|object>> at position 6 could not be parsed: Unknown type name 'class-string' at position 6 in ?array<string,array<class-string,class-string|callable|object>>.
Loading history...
585
     */
586
    public function setContextualBindings(?array $list): self
587
    {
588
        $this->properties->contextualBindings = $this->setPropertyWithTracking(
589
            self::contextualBindings,
590
            $list,
591
            self::DEFAULT_CONTEXTUAL_BINDINGS,
592
        );
593
594
        return $this;
595
    }
596
597
    /**
598
     * @internal Used by SetupInitializer - do not call directly
599
     *
600
     * @param ?array<class-string,list<callable>> $listeners
601
     */
602
    public function setSpecificListeners(?array $listeners): self
603
    {
604
        $this->properties->specificListeners = $listeners ?? self::DEFAULT_SPECIFIC_LISTENERS;
605
606
        return $this;
607
    }
608
609
    private function hasEventListeners(): bool
610
    {
611
        return ($this->properties->genericListeners !== null && $this->properties->genericListeners !== [])
612
            || ($this->properties->specificListeners !== null && $this->properties->specificListeners !== []);
613
    }
614
615
    /**
616
     * Helper method to set a property with change tracking and default value.
617
     *
618
     * @template T
619
     *
620
     * @param T $value
621
     * @param T $default
622
     *
623
     * @return T
624
     */
625
    private function setPropertyWithTracking(string $propertyName, mixed $value, mixed $default): mixed
626
    {
627
        $this->markPropertyAsChanged($propertyName, $value !== null);
628
        return $value ?? $default;
629
    }
630
631
    private function markPropertyAsChanged(string $name, bool $isChanged): void
632
    {
633
        if ($isChanged) {
634
            $this->changeTracker->markAsChanged($name);
635
        } else {
636
            $this->changeTracker->markAsUnchanged($name);
637
        }
638
    }
639
}
640