SetupGacela::fromFile()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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