Container::bindAlias()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Container;
15
16
use Override;
17
use Valkyrja\Container\Contract\Container as Contract;
18
use Valkyrja\Container\Contract\Service;
19
use Valkyrja\Container\Exception\InvalidArgumentException;
20
use Valkyrja\Container\Support\ProvidersAwareTrait;
21
22
use function assert;
23
use function class_exists;
24
25
/**
26
 * Class Container.
27
 *
28
 * @author Melech Mizrachi
29
 */
30
class Container implements Contract
31
{
32
    use ProvidersAwareTrait;
33
34
    /**
35
     * The aliases.
36
     *
37
     * @var array<class-string, class-string>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string, class-string> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string, class-string>.
Loading history...
38
     */
39
    protected array $aliases = [];
40
41
    /**
42
     * The instances.
43
     *
44
     * @var array<class-string, object>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string, object> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string, object>.
Loading history...
45
     */
46
    protected array $instances = [];
47
48
    /**
49
     * The services.
50
     *
51
     * @var array<class-string<Service>, class-string<Service>>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string<Servi... class-string<Service>> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string<Service>, class-string<Service>>.
Loading history...
52
     */
53
    protected array $services = [];
54
55
    /**
56
     * The service callables.
57
     *
58
     * @var array<class-string, callable(Container, mixed...):object>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string, call...iner, mixed...):object> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string, callable(Container, mixed...):object>.
Loading history...
59
     */
60
    protected array $callables = [];
61
62
    /**
63
     * The singletons.
64
     *
65
     * @var array<class-string, class-string>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string, class-string> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string, class-string>.
Loading history...
66
     */
67
    protected array $singletons = [];
68
69
    /**
70
     * Container constructor.
71
     */
72
    public function __construct(
73
        protected Data $data = new Data()
74
    ) {
75
        $this->aliases          = $data->aliases;
76
        $this->deferred         = $data->deferred;
77
        $this->deferredCallback = $data->deferredCallback;
78
        $this->services         = $data->services;
79
        $this->singletons       = $data->singletons;
80
        $this->registered       = [];
81
    }
82
83
    /**
84
     * @inheritDoc
85
     */
86
    #[Override]
87
    public function getData(): Data
88
    {
89
        return new Data(
90
            aliases: $this->aliases,
91
            deferred: $this->deferred,
92
            deferredCallback: $this->deferredCallback,
93
            services: [],
94
            singletons: [],
95
            providers: array_filter($this->providers, static fn (string $provider): bool => ! $provider::deferred()),
96
        );
97
    }
98
99
    /**
100
     * @inheritDoc
101
     */
102
    #[Override]
103
    public function setFromData(Data $data): void
104
    {
105
        $this->aliases          = array_merge($this->aliases, $data->aliases);
106
        $this->deferred         = array_merge($this->deferred, $data->deferred);
107
        $this->deferredCallback = array_merge($this->deferredCallback, $data->deferredCallback);
108
        $this->services         = array_merge($this->services, $data->services);
109
        $this->singletons       = array_merge($this->singletons, $data->singletons);
110
111
        array_map(
112
            [$this, 'register'],
113
            $data->providers
114
        );
115
    }
116
117
    /**
118
     * @inheritDoc
119
     *
120
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
121
     *
122
     * @psalm-suppress MoreSpecificImplementedParamType
123
     */
124
    #[Override]
125
    public function has(string $id): bool
126
    {
127
        $id = $this->getServiceIdInternal($id);
128
129
        return $this->isDeferred($id)
130
            || $this->isSingletonInternal($id)
131
            || $this->isServiceInternal($id)
132
            || $this->isCallableInternal($id)
133
            || $this->isAliasInternal($id);
134
    }
135
136
    /**
137
     * @inheritDoc
138
     *
139
     * @param class-string          $id      The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
140
     * @param class-string<Service> $service The service
141
     */
142
    #[Override]
143
    public function bind(string $id, string $service): static
144
    {
145
        assert(is_a($service, Service::class, true));
146
147
        $id = $this->getServiceIdInternal($id);
148
149
        /** @var class-string<Service> $id */
150
        $this->services[$id] = $service;
151
152
        return $this;
153
    }
154
155
    /**
156
     * @inheritDoc
157
     *
158
     * @param class-string $alias The alias
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
159
     * @param class-string $id    The service id to alias
160
     */
161
    #[Override]
162
    public function bindAlias(string $alias, string $id): static
163
    {
164
        $id = $this->getServiceIdInternal($id);
165
166
        $this->aliases[$alias] = $id;
167
168
        return $this;
169
    }
170
171
    /**
172
     * @inheritDoc
173
     *
174
     * @param class-string          $id        The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
175
     * @param class-string<Service> $singleton The singleton service
176
     */
177
    #[Override]
178
    public function bindSingleton(string $id, string $singleton): static
179
    {
180
        $internalId = $this->getServiceIdInternal($id);
181
182
        $this->singletons[$internalId] = $singleton;
183
184
        $this->bind($id, $singleton);
185
186
        return $this;
187
    }
188
189
    /**
190
     * @inheritDoc
191
     *
192
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
193
     */
194
    #[Override]
195
    public function setCallable(string $id, callable $callable): static
196
    {
197
        $id = $this->getServiceIdInternal($id);
198
199
        /** @var callable(Contract, mixed...):object $callable */
200
        $this->callables[$id] = $callable;
201
        $this->published[$id] = true;
202
203
        return $this;
204
    }
205
206
    /**
207
     * @inheritDoc
208
     *
209
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
210
     */
211
    #[Override]
212
    public function setSingleton(string $id, object $singleton): static
213
    {
214
        $id = $this->getServiceIdInternal($id);
215
216
        $this->singletons[$id] = $id;
217
        $this->instances[$id]  = $singleton;
218
        $this->published[$id]  = true;
219
220
        return $this;
221
    }
222
223
    /**
224
     * @inheritDoc
225
     *
226
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
227
     */
228
    #[Override]
229
    public function isAlias(string $id): bool
230
    {
231
        return $this->isAliasInternal($id);
232
    }
233
234
    /**
235
     * @inheritDoc
236
     *
237
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
238
     */
239
    #[Override]
240
    public function isCallable(string $id): bool
241
    {
242
        $id = $this->getServiceIdInternal($id);
243
244
        return $this->isCallableInternal($id);
245
    }
246
247
    /**
248
     * @inheritDoc
249
     *
250
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
251
     */
252
    #[Override]
253
    public function isService(string $id): bool
254
    {
255
        $id = $this->getServiceIdInternal($id);
256
257
        return $this->isServiceInternal($id);
258
    }
259
260
    /**
261
     * @inheritDoc
262
     *
263
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
264
     */
265
    #[Override]
266
    public function isSingleton(string $id): bool
267
    {
268
        $id = $this->getServiceIdInternal($id);
269
270
        return $this->isSingletonInternal($id);
271
    }
272
273
    /**
274
     * @inheritDoc
275
     *
276
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
277
     *
278
     * @psalm-suppress InvalidReturnType
279
     * @psalm-suppress InvalidReturnStatement
280
     * @psalm-suppress ImplementedReturnTypeMismatch
281
     * @psalm-suppress MoreSpecificImplementedParamType
282
     */
283
    #[Override]
284
    public function get(string $id, array $arguments = []): object
285
    {
286
        $id = $this->getServiceIdAndEnsurePublished($id);
287
288
        // If the service is a singleton
289
        if ($this->isSingletonInternal($id)) {
290
            // Return the singleton
291
            return $this->getSingletonWithoutChecks($id);
292
        }
293
294
        // If the service is a singleton
295
        if ($this->isCallableInternal($id)) {
296
            // Return the closure
297
            return $this->getCallableWithoutChecks($id, $arguments);
298
        }
299
300
        // If the service is in the container
301
        if ($this->isServiceInternal($id)) {
302
            /** @var class-string<Service> $id */
303
            // Return the made service
304
            return $this->getServiceWithoutChecks($id, $arguments);
305
        }
306
307
        if (class_exists($id)) {
308
            /** @psalm-suppress MixedMethodCall The developer should have passed the proper arguments */
309
            // Return a new object with the arguments
310
            return new $id(...$arguments);
311
        }
312
313
        throw new InvalidArgumentException("Provided $id does not exist");
314
    }
315
316
    /**
317
     * @inheritDoc
318
     *
319
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
320
     *
321
     * @psalm-suppress InvalidReturnType
322
     * @psalm-suppress InvalidReturnStatement
323
     * @psalm-suppress ImplementedReturnTypeMismatch
324
     */
325
    #[Override]
326
    public function getCallable(string $id, array $arguments = []): object
327
    {
328
        $id = $this->getServiceIdAndEnsurePublished($id);
329
330
        return $this->getCallableWithoutChecks($id, $arguments);
331
    }
332
333
    /**
334
     * @inheritDoc
335
     *
336
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
337
     *
338
     * @psalm-suppress InvalidReturnType
339
     * @psalm-suppress InvalidReturnStatement
340
     * @psalm-suppress ImplementedReturnTypeMismatch
341
     */
342
    #[Override]
343
    public function getService(string $id, array $arguments = []): Service
344
    {
345
        $id = $this->getServiceIdAndEnsurePublished($id);
346
347
        /** @var class-string<Service> $id */
348
349
        return $this->getServiceWithoutChecks($id, $arguments);
350
    }
351
352
    /**
353
     * @inheritDoc
354
     *
355
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
356
     *
357
     * @psalm-suppress InvalidReturnType
358
     * @psalm-suppress InvalidReturnStatement
359
     * @psalm-suppress ImplementedReturnTypeMismatch
360
     */
361
    #[Override]
362
    public function getSingleton(string $id): object
363
    {
364
        $id = $this->getServiceIdAndEnsurePublished($id);
365
366
        return $this->getSingletonWithoutChecks($id);
367
    }
368
369
    /**
370
     * Get an aliased service id if it exists.
371
     *
372
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
373
     *
374
     * @return class-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
375
     */
376
    protected function getAliasedServiceId(string $id): string
377
    {
378
        return $this->aliases[$id] ?? $id;
379
    }
380
381
    /**
382
     * Get a service id and ensure that it is published if it is provided.
383
     *
384
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
385
     *
386
     * @return class-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
387
     */
388
    protected function getServiceIdAndEnsurePublished(string $id): string
389
    {
390
        // Get an aliased service id if it exists
391
        $id = $this->getServiceIdInternal($id);
392
393
        $this->publishUnpublishedProvided($id);
394
395
        return $id;
396
    }
397
398
    /**
399
     * Get the context service id.
400
     *
401
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
402
     *
403
     * @return class-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
404
     */
405
    protected function getServiceIdInternal(string $id): string
406
    {
407
        return $this->getAliasedServiceId($id);
408
    }
409
410
    /**
411
     * Check whether a given service is an alias.
412
     *
413
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
414
     *
415
     * @return bool
416
     */
417
    protected function isAliasInternal(string $id): bool
418
    {
419
        return isset($this->aliases[$id]);
420
    }
421
422
    /**
423
     * Check whether a given service is bound to a callable.
424
     *
425
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
426
     *
427
     * @return bool
428
     */
429
    protected function isCallableInternal(string $id): bool
430
    {
431
        return isset($this->callables[$id]);
432
    }
433
434
    /**
435
     * Check whether a given service is a singleton.
436
     *
437
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
438
     *
439
     * @return bool
440
     */
441
    protected function isSingletonInternal(string $id): bool
442
    {
443
        return isset($this->singletons[$id]);
444
    }
445
446
    /**
447
     * Check whether a given service exists.
448
     *
449
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
450
     *
451
     * @return bool
452
     */
453
    protected function isServiceInternal(string $id): bool
454
    {
455
        return isset($this->services[$id]);
456
    }
457
458
    /**
459
     * Get a service bound to a callable from the container without trying to get an alias or ensuring published.
460
     *
461
     * @param class-string            $id        The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
462
     * @param array<array-key, mixed> $arguments [optional] The arguments
463
     *
464
     * @return object
465
     */
466
    protected function getCallableWithoutChecks(string $id, array $arguments = []): object
467
    {
468
        $closure = $this->callables[$id];
469
470
        return $closure($this, ...$arguments);
471
    }
472
473
    /**
474
     * Get a singleton from the container without trying to get an alias or ensuring published.
475
     *
476
     * @param class-string $id The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
477
     *
478
     * @return object
479
     */
480
    protected function getSingletonWithoutChecks(string $id): object
481
    {
482
        if (isset($this->instances[$id])) {
483
            return $this->instances[$id];
484
        }
485
486
        if (isset($this->services[$id])) {
487
            /** @var class-string<Service> $id */
488
            return $this->instances[$id] = $this->getServiceWithoutChecks($id);
489
        }
490
491
        throw new InvalidArgumentException("Provided $id does not exist");
492
    }
493
494
    /**
495
     * Get a service from the container without trying to get an alias or ensuring published.
496
     *
497
     * @param class-string<Service>   $id        The service id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Service> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Service>.
Loading history...
498
     * @param array<array-key, mixed> $arguments [optional] The arguments
499
     *
500
     * @return Service
501
     */
502
    protected function getServiceWithoutChecks(string $id, array $arguments = []): Service
503
    {
504
        $service = $this->services[$id];
505
506
        // Make the object by dispatching the service
507
        return $service::make($this, $arguments);
508
    }
509
}
510