SetupGacela   D
last analyzed

Complexity

Total Complexity 59

Size/Duplication

Total Lines 506
Duplicated Lines 0 %

Importance

Changes 18
Bugs 0 Features 0
Metric Value
eloc 130
c 18
b 0
f 0
dl 0
loc 506
rs 4.08
wmc 59

52 Methods

Rating   Name   Duplication   Size   Complexity  
A addServicesToExtend() 0 6 1
A setSuffixTypesBuilder() 0 5 1
A setBindingsFn() 0 5 1
A getConfigKeyValues() 0 3 1
A getFileCacheDirectory() 0 3 1
A isFileCacheEnabled() 0 3 1
A setSuffixTypesFn() 0 5 1
A setAppConfigFn() 0 5 1
A __construct() 0 6 1
A setAppConfigBuilder() 0 5 1
A getEventDispatcher() 0 3 1
A getProjectNamespaces() 0 3 1
A buildAppConfig() 0 5 1
A setShouldResetInMemoryCache() 0 9 1
A shouldResetInMemoryCache() 0 3 1
A fromGacelaConfig() 0 8 1
A fromCallable() 0 6 1
A buildBindings() 0 7 1
A setExternalServices() 0 6 1
A externalServices() 0 5 1
A setBindingsBuilder() 0 5 1
A setFileCacheDirectory() 0 9 1
A fromFile() 0 13 3
A getServicesToExtend() 0 3 1
A buildSuffixTypes() 0 5 1
A setProjectNamespaces() 0 9 1
A setGenericListeners() 0 5 1
A getGacelaConfigsToExtend() 0 3 1
A setAreEventListenersEnabled() 0 5 1
A setServicesToExtend() 0 9 1
A hasEventListeners() 0 4 4
A setFileCacheEnabled() 0 9 1
A isPropertyChanged() 0 3 1
A setGacelaConfigsToExtend() 0 9 1
A getFactories() 0 3 1
A mergeGacelaConfigsToExtend() 0 3 1
A getPlugins() 0 3 1
A merge() 0 3 1
A getSpecificListeners() 0 3 1
A setConfigKeyValues() 0 9 1
A mergeProjectNamespaces() 0 3 1
A setSpecificListeners() 0 5 1
A canCreateEventDispatcher() 0 4 2
A setPropertyWithTracking() 0 4 1
A getGenericListeners() 0 3 1
A mergeExternalServices() 0 3 1
A mergeConfigKeyValues() 0 3 1
A mergePlugins() 0 3 1
A setPlugins() 0 9 1
A setEventDispatcher() 0 5 1
A markPropertyAsChanged() 0 6 2
A setFactories() 0 9 1

How to fix   Complexity   

Complex Class

Complex classes like SetupGacela often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SetupGacela, and based on these observations, apply Extract Interface, too.

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