Passed
Push — main ( f0070b...7d5fab )
by Chema
03:59
created

SetupGacela::setPropertyWithTracking()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 3
dl 0
loc 4
rs 10
c 1
b 0
f 0
ccs 2
cts 2
cp 1
crap 1
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\SetupMerger;
14
use Gacela\Framework\Config\GacelaConfigBuilder\AppConfigBuilder;
15
use Gacela\Framework\Config\GacelaConfigBuilder\BindingsBuilder;
16
use Gacela\Framework\Config\GacelaConfigBuilder\SuffixTypesBuilder;
17
use Gacela\Framework\Event\Dispatcher\EventDispatcherInterface;
18
use Override;
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();
0 ignored issues
show
Bug introduced by
The property properties is declared read-only in Gacela\Framework\Bootstrap\SetupGacela.
Loading history...
40
        $this->changeTracker = new PropertyChangeTracker();
0 ignored issues
show
Bug introduced by
The property changeTracker is declared read-only in Gacela\Framework\Bootstrap\SetupGacela.
Loading history...
41
        $this->builderExecutor = new BuilderExecutor($this->properties);
0 ignored issues
show
Bug introduced by
The property builderExecutor is declared read-only in Gacela\Framework\Bootstrap\SetupGacela.
Loading history...
42
        $this->propertyMerger = new PropertyMerger($this);
0 ignored issues
show
Bug introduced by
The property propertyMerger is declared read-only in Gacela\Framework\Bootstrap\SetupGacela.
Loading history...
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
80 133
        return (new self())
81 133
            ->setExternalServices($dto->externalServices)
82 133
            ->setAppConfigBuilder($dto->appConfigBuilder)
83
            ->setSuffixTypesBuilder($dto->suffixTypesBuilder)
84
            ->setBindingsBuilder($dto->bindingsBuilder)
85
            ->setShouldResetInMemoryCache($dto->shouldResetInMemoryCache)
86
            ->setFileCacheEnabled($dto->fileCacheEnabled)
87
            ->setFileCacheDirectory($dto->fileCacheDirectory)
88
            ->setProjectNamespaces($dto->projectNamespaces)
89
            ->setConfigKeyValues($dto->configKeyValues)
90
            ->setAreEventListenersEnabled($dto->areEventListenersEnabled)
91
            ->setGenericListeners($dto->genericListeners)
92
            ->setSpecificListeners($dto->specificListeners)
93
            ->setGacelaConfigsToExtend($dto->gacelaConfigsToExtend)
94
            ->setPlugins($dto->plugins)
95
            ->setServicesToExtend($dto->servicesToExtend);
96
    }
97
98
    /**
99
     * @param array<string,class-string|object|callable> $array
100
     */
101
    public function setExternalServices(?array $array): self
102
    {
103
        $this->markPropertyAsChanged(self::externalServices, $array !== null);
104
        $this->properties->externalServices = $array; // No default fallback for external services
105
106 74
        return $this;
107
    }
108 74
109 74
    public function setAppConfigBuilder(AppConfigBuilder $builder): self
110
    {
111 74
        $this->properties->appConfigBuilder = $builder;
112
113
        return $this;
114 93
    }
115
116 93
    public function setSuffixTypesBuilder(SuffixTypesBuilder $builder): self
117
    {
118 93
        $this->properties->suffixTypesBuilder = $builder;
119
120 93
        return $this;
121 93
    }
122 93
123 93
    public function setBindingsBuilder(BindingsBuilder $builder): self
124 93
    {
125 93
        $this->properties->bindingsBuilder = $builder;
126 93
127 93
        return $this;
128 93
    }
129 93
130 93
    /**
131 93
     * @param callable(AppConfigBuilder):void $callable
132 93
     */
133 93
    public function setAppConfigFn(callable $callable): self
134 93
    {
135 93
        $this->properties->appConfigFn = $callable;
136
137
        return $this;
138
    }
139
140
    #[Override]
141 96
    public function buildAppConfig(AppConfigBuilder $builder): AppConfigBuilder
142
    {
143 96
        $builder = parent::buildAppConfig($builder);
144 96
145
        return $this->builderExecutor->buildAppConfig($builder);
146 96
    }
147
148
    /**
149 93
     * @param callable(BindingsBuilder,array<string,mixed>):void $callable
150
     */
151 93
    public function setBindingsFn(callable $callable): self
152
    {
153 93
        $this->properties->bindingsFn = $callable;
154
155
        return $this;
156 93
    }
157
158 93
    /**
159
     * Define the mapping between interfaces and concretions, so Gacela services will auto-resolve them automatically.
160 93
     *
161
     * @param array<string,class-string|object|callable> $externalServices
162
     */
163 93
    #[Override]
164
    public function buildBindings(
165 93
        BindingsBuilder $builder,
166
        array $externalServices,
167 93
    ): BindingsBuilder {
168
        $builder = parent::buildBindings($builder, $externalServices);
169
170
        return $this->builderExecutor->buildBindings($builder, $externalServices);
171
    }
172
173 3
    /**
174
     * @param callable(SuffixTypesBuilder):void $callable
175 3
     */
176
    public function setSuffixTypesFn(callable $callable): self
177 3
    {
178
        $this->properties->suffixTypesFn = $callable;
179
180 75
        return $this;
181
    }
182 75
183
    /**
184 75
     * Allow overriding gacela resolvable types.
185 67
     */
186
    #[Override]
187
    public function buildSuffixTypes(SuffixTypesBuilder $builder): SuffixTypesBuilder
188 75
    {
189
        $builder = parent::buildSuffixTypes($builder);
190 75
191
        return $this->builderExecutor->buildSuffixTypes($builder);
192
    }
193
194
    /**
195
     * @return array<string, class-string|object|callable>
196 3
     */
197
    #[Override]
198 3
    public function externalServices(): array
199
    {
200 3
        return array_merge(
201
            parent::externalServices(),
202
            $this->properties->externalServices ?? [],
203
        );
204
    }
205
206
    public function setShouldResetInMemoryCache(?bool $flag): self
207
    {
208 75
        $this->properties->shouldResetInMemoryCache = $this->setPropertyWithTracking(
209
            self::shouldResetInMemoryCache,
210
            $flag,
211
            self::DEFAULT_SHOULD_RESET_IN_MEMORY_CACHE,
212 75
        );
213
214 75
        return $this;
215 67
    }
216
217
    #[Override]
218 75
    public function shouldResetInMemoryCache(): bool
219 75
    {
220 75
        return $this->properties->shouldResetInMemoryCache ?? self::DEFAULT_SHOULD_RESET_IN_MEMORY_CACHE;
221 75
    }
222
223 75
    #[Override]
224
    public function isFileCacheEnabled(): bool
225
    {
226
        return $this->properties->fileCacheEnabled ?? self::DEFAULT_FILE_CACHE_ENABLED;
227
    }
228
229 3
    #[Override]
230
    public function getFileCacheDirectory(): string
231 3
    {
232
        return $this->properties->fileCacheDirectory ?? '';
233 3
    }
234
235
    public function setFileCacheDirectory(?string $dir): self
236
    {
237
        $this->properties->fileCacheDirectory = $this->setPropertyWithTracking(
238
            self::fileCacheDirectory,
239 75
            $dir,
240
            self::DEFAULT_FILE_CACHE_DIRECTORY,
241 75
        );
242
243 75
        return $this;
244 67
    }
245
246
    /**
247 75
     * @param ?list<string> $list
248
     */
249 75
    public function setProjectNamespaces(?array $list): self
250
    {
251
        $this->properties->projectNamespaces = $this->setPropertyWithTracking(
252
            self::projectNamespaces,
253
            $list,
254
            self::DEFAULT_PROJECT_NAMESPACES,
255 29
        );
256
257 29
        return $this;
258 29
    }
259 29
260 29
    /**
261
     * @return list<string>
262
     */
263 93
    #[Override]
264
    public function getProjectNamespaces(): array
265 93
    {
266 93
        return $this->properties->projectNamespaces ?? self::DEFAULT_PROJECT_NAMESPACES;
267
    }
268 93
269
    /**
270
     * @return array<string,mixed>
271 107
     */
272
    #[Override]
273 107
    public function getConfigKeyValues(): array
274
    {
275
        return $this->properties->configKeyValues ?? self::DEFAULT_CONFIG_KEY_VALUES;
276 49
    }
277
278 49
    #[Override]
279
    public function getEventDispatcher(): EventDispatcherInterface
280
    {
281 7
        return $this->properties->eventDispatcher ??= SetupEventDispatcher::getDispatcher($this);
282
    }
283 7
284
    /**
285
     * @return array<string,list<Closure>>
286 93
     */
287
    #[Override]
288 93
    public function getServicesToExtend(): array
289 93
    {
290
        return $this->properties->servicesToExtend ?? self::DEFAULT_SERVICES_TO_EXTEND;
291 93
    }
292
293
    public function setFileCacheEnabled(?bool $flag): self
294
    {
295
        $this->properties->fileCacheEnabled = $this->setPropertyWithTracking(
296
            self::fileCacheEnabled,
297 93
            $flag,
298
            self::DEFAULT_FILE_CACHE_ENABLED,
299 93
        );
300 93
301
        return $this;
302 93
    }
303
304
    public function canCreateEventDispatcher(): bool
305
    {
306
        return $this->properties->areEventListenersEnabled === true
307
            && $this->hasEventListeners();
308 45
    }
309
310 45
    /**
311
     * @param ?array<string,mixed> $configKeyValues
312
     */
313
    public function setConfigKeyValues(?array $configKeyValues): self
314
    {
315
        $this->properties->configKeyValues = $this->setPropertyWithTracking(
316 107
            self::configKeyValues,
317
            $configKeyValues,
318 107
            self::DEFAULT_CONFIG_KEY_VALUES,
319
        );
320
321 70
        return $this;
322
    }
323 70
324
    /**
325
     * @return array<class-string,list<callable>>|null
326
     */
327
    public function getSpecificListeners(): ?array
328
    {
329 120
        return $this->properties->specificListeners;
330
    }
331 120
332
    /**
333
     * @return list<callable>|null
334 93
     */
335
    public function getGenericListeners(): ?array
336 93
    {
337 93
        return $this->properties->genericListeners;
338
    }
339 93
340
    public function isPropertyChanged(string $name): bool
341
    {
342 70
        return $this->changeTracker->isChanged($name);
343
    }
344 70
345 70
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): self
346
    {
347
        $this->properties->eventDispatcher = $eventDispatcher;
348
349
        return $this;
350
    }
351 93
352
    #[Override]
353 93
    public function merge(self $other): self
354 93
    {
355
        return (new SetupMerger($this))->merge($other);
356 93
    }
357
358
    /**
359
     * @param list<Closure> $servicesToExtend
360
     */
361
    public function addServicesToExtend(string $serviceId, array $servicesToExtend): self
362 16
    {
363
        $this->properties->servicesToExtend[$serviceId] ??= [];
364 16
        $this->properties->servicesToExtend[$serviceId] = [...$this->properties->servicesToExtend[$serviceId], ...$servicesToExtend];
365
366
        return $this;
367
    }
368
369
    /**
370 16
     * @param array<string,class-string|object|callable> $list
371
     */
372 16
    public function mergeExternalServices(array $list): void
373
    {
374
        $this->propertyMerger->mergeExternalServices($list);
375 29
    }
376
377 29
    /**
378
     * @param list<string> $list
379
     */
380 29
    public function mergeProjectNamespaces(array $list): void
381
    {
382 29
        $this->propertyMerger->mergeProjectNamespaces($list);
383
    }
384 29
385
    /**
386
     * @param array<string,mixed> $list
387 29
     */
388
    public function mergeConfigKeyValues(array $list): void
389 29
    {
390
        $this->propertyMerger->mergeConfigKeyValues($list);
391
    }
392
393
    /**
394
     * @param list<class-string> $list
395 1
     */
396
    public function mergeGacelaConfigsToExtend(array $list): void
397 1
    {
398 1
        $this->propertyMerger->mergeGacelaConfigsToExtend($list);
399 1
    }
400 1
401 1
    /**
402
     * @param list<class-string|callable> $list
403 1
     */
404
    public function mergePlugins(array $list): void
405
    {
406 29
        $this->propertyMerger->mergePlugins($list);
407
    }
408 29
409
    /**
410
     * @return list<class-string>
411 1
     */
412
    #[Override]
413 1
    public function getGacelaConfigsToExtend(): array
414
    {
415
        return $this->properties->gacelaConfigsToExtend ?? self::DEFAULT_GACELA_CONFIGS_TO_EXTEND;
416 1
    }
417
418 1
    /**
419
     * @return list<class-string|callable>
420
     */
421
    #[Override]
422
    public function getPlugins(): array
423
    {
424 1
        return $this->properties->plugins ?? self::DEFAULT_PLUGINS;
425
    }
426 1
427 1
    /**
428 1
     * @internal Used by PropertyMerger - do not call directly
429
     *
430
     * @param ?list<class-string> $list
431
     */
432
    public function setGacelaConfigsToExtend(?array $list): self
433
    {
434 1
        $this->properties->gacelaConfigsToExtend = $this->setPropertyWithTracking(
435
            self::gacelaConfigsToExtend,
436 1
            $list,
437
            self::DEFAULT_GACELA_CONFIGS_TO_EXTEND,
438
        );
439
440
        return $this;
441
    }
442 1
443
    /**
444 1
     * @internal Used by PropertyMerger - do not call directly
445
     *
446
     * @param ?list<class-string|callable> $list
447
     */
448
    public function setPlugins(?array $list): self
449
    {
450 107
        $this->properties->plugins = $this->setPropertyWithTracking(
451
            self::plugins,
452 107
            $list,
453
            self::DEFAULT_PLUGINS,
454
        );
455 93
456
        return $this;
457 93
    }
458
459 93
    private function setAreEventListenersEnabled(?bool $flag): self
460
    {
461
        $this->properties->areEventListenersEnabled = $flag ?? self::DEFAULT_ARE_EVENT_LISTENERS_ENABLED;
462 70
463
        return $this;
464 70
    }
465 70
466
    private function hasEventListeners(): bool
467
    {
468
        return ($this->properties->genericListeners !== null && $this->properties->genericListeners !== [])
469
            || ($this->properties->specificListeners !== null && $this->properties->specificListeners !== []);
470
    }
471 93
472
    /**
473 93
     * @param ?list<callable> $listeners
474
     */
475 93
    private function setGenericListeners(?array $listeners): self
476
    {
477
        $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...
478
479
        return $this;
480
    }
481 93
482
    /**
483 93
     * @param ?array<string,list<Closure>> $list
484 93
     */
485
    private function setServicesToExtend(?array $list): self
486 93
    {
487
        $this->properties->servicesToExtend = $this->setPropertyWithTracking(
488
            self::servicesToExtend,
489
            $list,
490
            self::DEFAULT_SERVICES_TO_EXTEND,
491
        );
492 93
493
        return $this;
494 93
    }
495 93
496
    /**
497 93
     * @param ?array<class-string,list<callable>> $listeners
498
     */
499
    private function setSpecificListeners(?array $listeners): self
500
    {
501
        $this->properties->specificListeners = $listeners ?? self::DEFAULT_SPECIFIC_LISTENERS;
502
503 93
        return $this;
504
    }
505 93
506 93
    /**
507
     * Helper method to set a property with change tracking and default value.
508 93
     *
509
     * @template T
510
     *
511
     * @param T $value
512
     * @param T $default
513
     *
514 93
     * @return T
515
     */
516 93
    private function setPropertyWithTracking(string $propertyName, mixed $value, mixed $default): mixed
517
    {
518 93
        $this->markPropertyAsChanged($propertyName, $value !== null);
519
        return $value ?? $default;
520
    }
521 96
522
    private function markPropertyAsChanged(string $name, bool $isChanged): void
523 96
    {
524
        if ($isChanged) {
525
            $this->changeTracker->markAsChanged($name);
526
        } else {
527
            $this->changeTracker->markAsUnchanged($name);
528
        }
529
    }
530
}
531