Test Failed
Push — feat/contextual-bindings ( c2fffd )
by Chema
05:29
created

GacelaConfig::when()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 6
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework\Bootstrap;
6
7
use Closure;
8
use Gacela\Container\ContextualBindingBuilder;
9
use Gacela\Framework\Bootstrap\Setup\GacelaConfigTransfer;
10
use Gacela\Framework\Config\ConfigReaderInterface;
11
use Gacela\Framework\Config\GacelaConfigBuilder\AppConfigBuilder;
12
use Gacela\Framework\Config\GacelaConfigBuilder\BindingsBuilder;
13
use Gacela\Framework\Config\GacelaConfigBuilder\SuffixTypesBuilder;
14
use Gacela\Framework\Event\GacelaEventInterface;
15
16
final class GacelaConfig
17
{
18
    private readonly AppConfigBuilder $appConfigBuilder;
19
20
    private readonly SuffixTypesBuilder $suffixTypesBuilder;
21
22
    private readonly BindingsBuilder $bindingsBuilder;
23
24
    private ?bool $shouldResetInMemoryCache = null;
25
26
    private ?bool $fileCacheEnabled = null;
27
28
    private ?string $fileCacheDirectory = null;
29
30
    /** @var list<string> */
31
    private ?array $projectNamespaces = null;
32
33
    /** @var array<string,mixed> */
34
    private ?array $configKeyValues = null;
35
36
    private ?bool $areEventListenersEnabled = null;
37
38
    /** @var list<callable> */
39
    private ?array $genericListeners = null;
40
41
    /** @var array<class-string,list<callable>> */
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string,list<callable>> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string,list<callable>>.
Loading history...
42
    private ?array $specificListeners = null;
43
44
    /** @var list<class-string> */
45
    private ?array $gacelaConfigsToExtend = null;
46
47
    /** @var list<class-string|callable> */
48
    private ?array $plugins = null;
49
50
    /** @var array<string,list<Closure>> */
51
    private array $servicesToExtend = [];
52
53
    /** @var array<string,Closure> */
54
    private array $factories = [];
55
56
    /** @var array<string,Closure> */
57
    private array $protectedServices = [];
58
59
    /** @var array<string,string> */
60
    private array $aliases = [];
61
62
    /** @var 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...
63
    private array $contextualBindings = [];
64
65
    /**
66
     * @param array<string,class-string|object|callable> $externalServices
67
     */
68
    public function __construct(private array $externalServices = [])
69
    {
70
        $this->appConfigBuilder = new AppConfigBuilder();
0 ignored issues
show
Bug introduced by
The property appConfigBuilder is declared read-only in Gacela\Framework\Bootstrap\GacelaConfig.
Loading history...
71
        $this->suffixTypesBuilder = new SuffixTypesBuilder();
0 ignored issues
show
Bug introduced by
The property suffixTypesBuilder is declared read-only in Gacela\Framework\Bootstrap\GacelaConfig.
Loading history...
72
        $this->bindingsBuilder = new BindingsBuilder();
0 ignored issues
show
Bug introduced by
The property bindingsBuilder is declared read-only in Gacela\Framework\Bootstrap\GacelaConfig.
Loading history...
73
    }
74
75
    /**
76
     * Define 'config/*.php' as path, and 'config/local.php' as local path for the configuration.
77
     *
78
     * @codeCoverageIgnore
79
     *
80
     * @return Closure(GacelaConfig):void
81
     */
82
    public static function defaultPhpConfig(): callable
83
    {
84
        return static function (self $config): void {
85
            $config->addAppConfig('config/*.php', 'config/local.php');
86
        };
87
    }
88
89
    /**
90
     * Define the path where the configuration will be stored.
91
     *
92
     * @param string $path define the path where Gacela will read all the config files
93
     * @param string $pathLocal define the path where Gacela will read the local config file
94
     * @param class-string<ConfigReaderInterface>|ConfigReaderInterface|null $reader Define the reader class which will read and parse the config files
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<ConfigReade...figReaderInterface|null at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<ConfigReaderInterface>|ConfigReaderInterface|null.
Loading history...
95
     */
96
    public function addAppConfig(string $path, string $pathLocal = '', string|ConfigReaderInterface|null $reader = null): self
97
    {
98
        $this->appConfigBuilder->add($path, $pathLocal, $reader);
99
100
        return $this;
101
    }
102
103
    /**
104
     * Allow overriding gacela facade suffixes.
105
     */
106
    public function addSuffixTypeFacade(string $suffix): self
107
    {
108
        $this->suffixTypesBuilder->addFacade($suffix);
109
110
        return $this;
111
    }
112
113
    /**
114
     * Allow overriding gacela factory suffixes.
115
     */
116
    public function addSuffixTypeFactory(string $suffix): self
117
    {
118
        $this->suffixTypesBuilder->addFactory($suffix);
119
120
        return $this;
121
    }
122
123
    /**
124
     * Allow overriding gacela config suffixes.
125
     */
126
    public function addSuffixTypeConfig(string $suffix): self
127
    {
128
        $this->suffixTypesBuilder->addConfig($suffix);
129
130
        return $this;
131
    }
132
133
    /**
134
     * Allow overriding gacela dependency provider suffixes.
135
     */
136
    public function addSuffixTypeProvider(string $suffix): self
137
    {
138
        $this->suffixTypesBuilder->addProvider($suffix);
139
140
        return $this;
141
    }
142
143
    /**
144
     * @deprecated in favor of `$this->addBinding(key, value)`
145
     * It will be removed in the next release
146
     *
147
     * @param class-string $key
148
     * @param class-string|object|callable $value
149
     */
150
    public function addMappingInterface(string $key, string|object|callable $value): self
151
    {
152
        return $this->addBinding($key, $value);
153
    }
154
155
    /**
156
     * Bind a key class or interface name to be resolved by Gacela automatically.
157
     *
158
     * @param class-string $key
159
     * @param class-string|object|callable $value
160
     */
161
    public function addBinding(string $key, string|object|callable $value): self
162
    {
163
        $this->bindingsBuilder->bind($key, $value);
164
165
        return $this;
166
    }
167
168
    /**
169
     * Useful to pass services while bootstrapping Gacela to the gacela.php config file.
170
     *
171
     * @param class-string|object|callable $value
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|object|callable at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|object|callable.
Loading history...
172
     */
173
    public function addExternalService(string $key, $value): self
174
    {
175
        $this->externalServices[$key] = $value;
176
177
        return $this;
178
    }
179
180
    /**
181
     * Get an external service from its defined key, previously added using `addExternalService()`.
182
     *
183
     * @return class-string|object|callable
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|object|callable at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|object|callable.
Loading history...
184
     */
185
    public function getExternalService(string $key)
186
    {
187
        return $this->externalServices[$key];
188
    }
189
190
    /**
191
     * Enable resetting the memory cache on each setup. Useful for functional tests.
192
     */
193
    public function resetInMemoryCache(): self
194
    {
195
        $this->shouldResetInMemoryCache = true;
196
197
        return $this;
198
    }
199
200
    /**
201
     * Shortcut to setFileCache(true)
202
     */
203
    public function enableFileCache(?string $dir = null): self
204
    {
205
        return $this->setFileCache(true, $dir);
206
    }
207
208
    /**
209
     * Define whether the file cache flag is enabled,
210
     * and the file cache directory.
211
     */
212
    public function setFileCache(bool $enabled, ?string $dir = null): self
213
    {
214
        $this->fileCacheEnabled = $enabled;
215
        $this->fileCacheDirectory = $dir;
216
217
        return $this;
218
    }
219
220
    /**
221
     * Define a list of project namespaces.
222
     *
223
     * @param list<string> $list
224
     */
225
    public function setProjectNamespaces(array $list): self
226
    {
227
        $this->projectNamespaces = $list;
0 ignored issues
show
Documentation Bug introduced by
It seems like $list of type array is incompatible with the declared type Gacela\Framework\Bootstrap\list 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...
228
229
        return $this;
230
    }
231
232
    /**
233
     * Add/replace an existent configuration key with a specific value.
234
     */
235
    public function addAppConfigKeyValue(string $key, mixed $value): self
236
    {
237
        $this->configKeyValues[$key] = $value;
238
239
        return $this;
240
    }
241
242
    /**
243
     * Add/replace a list of existent configuration keys with a specific value.
244
     *
245
     * @param array<string, mixed> $config
246
     */
247
    public function addAppConfigKeyValues(array $config): self
248
    {
249
        $this->configKeyValues = array_merge($this->configKeyValues ?? [], $config);
250
251
        return $this;
252
    }
253
254
    /**
255
     * Do not dispatch any event in the application.
256
     */
257
    public function disableEventListeners(): self
258
    {
259
        $this->areEventListenersEnabled = false;
260
261
        return $this;
262
    }
263
264
    /**
265
     * Register a generic listener when any event happens.
266
     * The callable argument must be the type `GacelaEventInterface`.
267
     *
268
     * @param callable(GacelaEventInterface):void $listener
269
     */
270
    public function registerGenericListener(callable $listener): self
271
    {
272
        $this->genericListeners ??= [];
273
        $this->genericListeners[] = $listener;
274
275
        return $this;
276
    }
277
278
    /**
279
     * Register a listener when some event happens.
280
     *
281
     * @param class-string $event
282
     * @param callable(GacelaEventInterface):void $listener
283
     */
284
    public function registerSpecificListener(string $event, callable $listener): self
285
    {
286
        $this->specificListeners[$event] ??= [];
287
        $this->specificListeners[$event][] = $listener;
288
289
        return $this;
290
    }
291
292
    public function extendService(string $id, Closure $service): self
293
    {
294
        $this->servicesToExtend[$id] ??= [];
295
        $this->servicesToExtend[$id][] = $service;
296
297
        return $this;
298
    }
299
300
    /**
301
     * Register a factory service that creates a new instance on each call.
302
     * Unlike regular services (which are singletons), factory services return
303
     * a new instance every time they are resolved from the container.
304
     *
305
     * @param string $id The service identifier (usually a class name or interface)
306
     * @param Closure $factory The factory closure that creates the service instance
307
     *
308
     * @return $this
309
     */
310
    public function addFactory(string $id, Closure $factory): self
311
    {
312
        $this->factories[$id] = $factory;
313
314
        return $this;
315
    }
316
317
    /**
318
     * Register a protected service that cannot be extended.
319
     * Protected services are stored as closures and won't be invoked by the container,
320
     * making them useful for storing callable configurations.
321
     *
322
     * @param string $id The service identifier
323
     * @param Closure $service The closure to protect
324
     *
325
     * @return $this
326
     */
327
    public function addProtected(string $id, Closure $service): self
328
    {
329
        $this->protectedServices[$id] = $service;
330
331
        return $this;
332
    }
333
334
    /**
335
     * Create an alias for a service.
336
     * This allows you to reference the same service with different names.
337
     *
338
     * @param string $alias The alias name
339
     * @param string $id The actual service identifier
340
     *
341
     * @return $this
342
     */
343
    public function addAlias(string $alias, string $id): self
344
    {
345
        $this->aliases[$alias] = $id;
346
347
        return $this;
348
    }
349
350
    /**
351
     * Define contextual bindings - different implementations based on the requesting class.
352
     * This allows you to provide different implementations of an interface depending on
353
     * which class is requesting it.
354
     *
355
     * Example:
356
     * ```php
357
     * $config->when(UserController::class)
358
     *     ->needs(LoggerInterface::class)
359
     *     ->give(FileLogger::class);
360
     * ```
361
     *
362
     * @param class-string|list<class-string> $concrete The class(es) that need the binding
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|list<class-string> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|list<class-string>.
Loading history...
363
     */
364
    public function when(string|array $concrete): ContextualBindingBuilder
365
    {
366
        $builder = new ContextualBindingBuilder($this->contextualBindings);
367
        $builder->when($concrete);
368
369
        return $builder;
370
    }
371
372
    /**
373
     * Add a new invokable class that can extend the GacelaConfig object.
374
     *
375
     * This configClass will receive the GacelaConfig object as argument to the __invoke() method.
376
     * ```
377
     * __invoke(GacelaConfig $config): void
378
     * ```
379
     *
380
     * @param class-string $className
381
     */
382
    public function extendGacelaConfig(string $className): self
383
    {
384
        $this->gacelaConfigsToExtend[] = $className;
385
386
        return $this;
387
    }
388
389
    /**
390
     * @param list<class-string> $list
391
     */
392
    public function extendGacelaConfigs(array $list): self
393
    {
394
        $this->gacelaConfigsToExtend = array_merge($this->gacelaConfigsToExtend ?? [], $list);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge($this->gacel...tend ?? array(), $list) of type array is incompatible with the declared type Gacela\Framework\Bootstrap\list of property $gacelaConfigsToExtend.

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...
395
396
        return $this;
397
    }
398
399
    /**
400
     * @param class-string|callable $plugin
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|callable at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|callable.
Loading history...
401
     */
402
    public function addPlugin(string|callable $plugin): self
403
    {
404
        $this->plugins[] = $plugin;
405
406
        return $this;
407
    }
408
409
    /**
410
     * @param list<class-string|callable> $list
411
     */
412
    public function addPlugins(array $list): self
413
    {
414
        $this->plugins = array_merge($this->plugins ?? [], $list);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge($this->plugins ?? array(), $list) of type array is incompatible with the declared type Gacela\Framework\Bootstrap\list of property $plugins.

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...
415
416
        return $this;
417
    }
418
419
    /**
420
     * @internal
421
     */
422
    public function toTransfer(): GacelaConfigTransfer
423
    {
424
        return new GacelaConfigTransfer(
425
            $this->appConfigBuilder,
426
            $this->suffixTypesBuilder,
427
            $this->bindingsBuilder,
428
            $this->externalServices,
429
            $this->shouldResetInMemoryCache,
430
            $this->fileCacheEnabled,
431
            $this->fileCacheDirectory,
432
            $this->projectNamespaces,
433
            $this->configKeyValues,
434
            $this->genericListeners,
435
            $this->specificListeners,
436
            $this->areEventListenersEnabled,
437
            $this->gacelaConfigsToExtend,
438
            $this->plugins,
439
            $this->servicesToExtend,
440
            $this->factories,
441
            $this->protectedServices,
442
            $this->aliases,
443
            $this->contextualBindings,
444
        );
445
    }
446
}
447