Passed
Pull Request — master (#38)
by Melech
05:50 queued 01:43
created

Container::setFromData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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