Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
212 / 212
96.39% covered (success)
96.39%
160 / 166
49.36% covered (danger)
49.36%
77 / 156
84.00% covered (warning)
84.00%
21 / 25
CRAP
0.00% covered (danger)
0.00%
0 / 1
Container
100.00% covered (success)
100.00%
212 / 212
96.39% covered (success)
96.39%
160 / 166
49.36% covered (danger)
49.36%
77 / 156
100.00% covered (success)
100.00%
25 / 25
1046.52
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 scope
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 reset
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 has
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
5 / 5
75.00% covered (warning)
75.00%
3 / 4
100.00% covered (success)
100.00%
1 / 1
3.14
 entries
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
6 / 6
75.00% covered (warning)
75.00%
3 / 4
100.00% covered (success)
100.00%
1 / 1
4.25
 entry
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 get
100.00% covered (success)
100.00%
30 / 30
95.24% covered (success)
95.24%
20 / 21
66.67% covered (warning)
66.67%
8 / 12
100.00% covered (success)
100.00%
1 / 1
12.00
 definition
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 add
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addEntry
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 tag
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
9 / 9
57.14% covered (warning)
57.14%
4 / 7
100.00% covered (success)
100.00%
1 / 1
6.97
 new
100.00% covered (success)
100.00%
11 / 11
86.67% covered (warning)
86.67%
13 / 15
21.05% covered (danger)
21.05%
4 / 19
100.00% covered (success)
100.00%
1 / 1
23.71
 resolveEntry
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
9 / 9
57.14% covered (warning)
57.14%
4 / 7
100.00% covered (success)
100.00%
1 / 1
6.97
 resolutionContainers
100.00% covered (success)
100.00%
5 / 5
77.78% covered (warning)
77.78%
7 / 9
75.00% covered (warning)
75.00%
3 / 4
100.00% covered (success)
100.00%
1 / 1
4.25
 materialize
100.00% covered (success)
100.00%
35 / 35
95.24% covered (success)
95.24%
20 / 21
39.13% covered (danger)
39.13%
9 / 23
100.00% covered (success)
100.00%
1 / 1
32.55
 applyCalls
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
4 / 4
66.67% covered (warning)
66.67%
2 / 3
100.00% covered (success)
100.00%
1 / 1
2.15
 resetScope
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
12 / 12
13.89% covered (danger)
13.89%
5 / 36
100.00% covered (success)
100.00%
1 / 1
20.96
 resetIfNeeded
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 trackAndReturn
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
5 / 5
75.00% covered (warning)
75.00%
3 / 4
100.00% covered (success)
100.00%
1 / 1
3.14
 findEntry
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 taggedParent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
6 / 6
75.00% covered (warning)
75.00%
3 / 4
100.00% covered (success)
100.00%
1 / 1
3.14
 root
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 assertMutable
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 seal
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
4 / 4
66.67% covered (warning)
66.67%
2 / 3
100.00% covered (success)
100.00%
1 / 1
2.15
 isRoot
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
5 / 5
50.00% covered (danger)
50.00%
2 / 4
100.00% covered (success)
100.00%
1 / 1
4.12
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Container;
6
7use Celema\Container\Exception\ContainerException;
8use Celema\Container\Exception\NotFoundException;
9use Celema\Wire\CallableResolver;
10use Celema\Wire\Creator;
11use Celema\Wire\Exception\WireException;
12use Celema\Wire\WireContainer;
13use Closure;
14use Override;
15use Psr\Container\ContainerExceptionInterface;
16use Psr\Container\ContainerInterface as PsrContainer;
17use Throwable;
18
19/** @psalm-api */
20class Container implements WireContainer
21{
22    protected Creator $creator;
23    protected readonly ?PsrContainer $wrappedContainer;
24    protected bool $sealed = false;
25
26    /** @var array<string, Entry> */
27    protected array $entries = [];
28
29    /** @var array<string, mixed> */
30    protected array $instances = [];
31
32    /** @var array<non-empty-string, self> */
33    protected array $tags = [];
34
35    /** @var array<int, Resettable> */
36    protected array $usedResettables = [];
37
38    public function __construct(
39        public readonly bool $autowire = true,
40        ?PsrContainer $container = null,
41        protected readonly string $tag = '',
42        protected readonly ?Container $parent = null,
43        protected readonly bool $isScope = false,
44    ) {
45        if ($container) {
46            $this->wrappedContainer = $container;
47            $this->add(PsrContainer::class, $container);
48            $this->add($container::class, $container);
49        } else {
50            $this->wrappedContainer = null;
51            $this->add(PsrContainer::class, $this);
52        }
53        $this->add(Container::class, $this);
54        $this->creator = new Creator($this);
55    }
56
57    public function scope(): Container
58    {
59        $root = $this->root();
60
61        if (!$root->sealed) {
62            $root->seal();
63        }
64
65        return new self(
66            autowire: $root->autowire,
67            parent: $root,
68            isScope: true,
69        );
70    }
71
72    public function reset(): void
73    {
74        if (!$this->isScope) {
75            return;
76        }
77
78        $resetIds = [];
79        $this->resetScope($resetIds);
80    }
81
82    #[Override]
83    public function has(string $id): bool
84    {
85        return (
86            isset($this->entries[$id])
87                || $this->parent?->has($id)
88                || $this->wrappedContainer?->has($id)
89        );
90    }
91
92    /** @return list<string> */
93    public function entries(bool $includeContainer = false): array
94    {
95        $inherited = $this->taggedParent()?->entries(true) ?? [];
96        $keys = $inherited === []
97            ? array_keys($this->entries)
98            : array_values(array_unique([...array_keys($this->entries), ...$inherited]));
99
100        if ($includeContainer) {
101            return $keys;
102        }
103
104        return array_values(array_filter(
105            $keys,
106            static fn($item) => $item !== PsrContainer::class && !is_subclass_of($item, PsrContainer::class),
107        ));
108    }
109
110    public function entry(string $id): Entry
111    {
112        $entry = $this->entries[$id] ?? null;
113
114        if ($entry !== null) {
115            return $entry;
116        }
117
118        $parent = $this->taggedParent();
119
120        if ($parent !== null) {
121            return $parent->entry($id);
122        }
123
124        throw new NotFoundException('Unresolvable entry - id: ' . $id);
125    }
126
127    #[Override]
128    public function get(string $id): mixed
129    {
130        try {
131            if (array_key_exists($id, $this->instances)) {
132                return $this->trackAndReturn($this->instances[$id]);
133            }
134
135            $resolved = $this->findEntry($id);
136
137            if ($resolved !== null) {
138                return $this->trackAndReturn(
139                    $this->resolveEntry(
140                        entryOwner: $resolved[0],
141                        entry: $resolved[1],
142                        id: $id,
143                        requester: $this,
144                    ),
145                );
146            }
147
148            $wrappedContainer = $this->root()->wrappedContainer;
149
150            if ($wrappedContainer?->has($id)) {
151                return $this->trackAndReturn($wrappedContainer->get($id));
152            }
153
154            // Autowiring: $id does not exists as an entry in the container
155            if ($this->autowire && class_exists($id)) {
156                return $this->trackAndReturn($this->creator->create($id));
157            }
158        } catch (WireException $e) {
159            throw new NotFoundException(
160                'Unresolvable id: ' . $id . ' - Details: ' . $e->getMessage(),
161                previous: $e,
162            );
163        } catch (ContainerExceptionInterface $e) {
164            throw $e;
165        } catch (Throwable $e) {
166            throw new ContainerException(
167                'Unresolvable id: ' . $id . ' - ' . $e->getMessage(),
168                previous: $e,
169            );
170        }
171
172        throw new NotFoundException('Unresolvable id: ' . $id);
173    }
174
175    #[Override]
176    public function definition(string $id): mixed
177    {
178        $resolved = $this->findEntry($id);
179        $entry = $resolved[1] ?? null;
180
181        if ($entry !== null) {
182            return $entry->definition();
183        }
184
185        throw new NotFoundException('Unresolvable definition - id: ' . $id);
186    }
187
188    /**
189     * @param non-empty-string $id
190     */
191    public function add(
192        string $id,
193        mixed $value = null,
194    ): Entry {
195        $this->assertMutable();
196        $entry = new Entry($id, $value ?? $id);
197        $this->entries[$id] = $entry;
198        unset($this->instances[$id]);
199
200        return $entry;
201    }
202
203    public function addEntry(
204        Entry $entry,
205    ): Entry {
206        $this->assertMutable();
207        $this->entries[$entry->id] = $entry;
208        unset($this->instances[$entry->id]);
209
210        return $entry;
211    }
212
213    /** @param non-empty-string $tag */
214    public function tag(string $tag): Container
215    {
216        if (isset($this->tags[$tag])) {
217            return $this->tags[$tag];
218        }
219
220        if ($this->isRoot() && $this->sealed) {
221            throw new ContainerException('The root container is sealed after scope() was called');
222        }
223
224        $parent = $this;
225        $isScope = false;
226
227        if ($this->isScope) {
228            $root = $this->root();
229            $parent = $root->tags[$tag] ?? $root;
230            $isScope = true;
231        }
232
233        $this->tags[$tag] = new self(
234            autowire: $this->autowire,
235            tag: $tag,
236            parent: $parent,
237            isScope: $isScope,
238        );
239
240        return $this->tags[$tag];
241    }
242
243    public function new(string $id, mixed ...$args): object
244    {
245        $entry = $this->entries[$id] ?? null;
246
247        if ($entry) {
248            /** @var mixed $value */
249            $value = $entry->definition();
250
251            if (is_string($value)) {
252                if (interface_exists($value)) {
253                    return $this->new($value, ...$args);
254                }
255
256                if (class_exists($value)) {
257                    /** @psalm-suppress MixedMethodCall */
258                    return new $value(...$args);
259                }
260            }
261        }
262
263        if (class_exists($id)) {
264            /** @psalm-suppress MixedMethodCall */
265            return new $id(...$args);
266        }
267
268        throw new NotFoundException('Cannot instantiate ' . $id);
269    }
270
271    protected function resolveEntry(
272        Container $entryOwner,
273        Entry $entry,
274        string $id,
275        Container $requester,
276    ): mixed {
277        if ($entry->shouldReturnValue()) {
278            return $entry->definition();
279        }
280
281        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
282            $entry,
283            $entryOwner,
284            $requester,
285        );
286
287        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
288            return $cacheContainer->instances[$id];
289        }
290
291        // materialize() intentionally returns mixed because container entries may resolve to any value type.
292        /** @psalm-suppress MixedAssignment */
293        $result = $this->materialize($entry, $resolutionContext);
294
295        if ($cacheContainer !== null) {
296            $cacheContainer->instances[$id] = $result;
297        }
298
299        return $result;
300    }
301
302    /**
303     * @return array{0: null|Container, 1: Container}
304     */
305    protected function resolutionContainers(
306        Entry $entry,
307        Container $entryOwner,
308        Container $requester,
309    ): array {
310        return match ($entry->getLifetime()) {
311            Lifetime::Shared => [$entryOwner, $entryOwner],
312            Lifetime::Scoped => [$requester, $requester],
313            Lifetime::Transient => [null, $requester],
314        };
315    }
316
317    protected function materialize(Entry $entry, Container $context): mixed
318    {
319        /** @var mixed $value */
320        $value = $entry->definition();
321
322        if (is_string($value)) {
323            if (class_exists($value)) {
324                $constructor = $entry->getConstructor();
325                $args = $entry->getArgs();
326
327                if (isset($args)) {
328                    // Don't autowire if $args are given
329                    if ($args instanceof Closure) {
330                        $args = $args(...new CallableResolver($context->creator)->resolve($args));
331
332                        return $this->applyCalls($entry, $context->creator->create($value, $args), $context);
333                    }
334
335                    return $this->applyCalls(
336                        $entry,
337                        $context->creator->create(
338                            $value,
339                            predefinedArgs: $args,
340                            constructor: $constructor ?? '',
341                        ),
342                        $context,
343                    );
344                }
345
346                return $this->applyCalls(
347                    $entry,
348                    $context->creator->create($value, constructor: $constructor ?? ''),
349                    $context,
350                );
351            }
352
353            if ($context->has($value)) {
354                return $context->get($value);
355            }
356        }
357
358        if ($value instanceof Closure) {
359            $args = $entry->getArgs();
360
361            if (is_null($args)) {
362                $args = new CallableResolver($context->creator)->resolve($value);
363            } elseif ($args instanceof Closure) {
364                $args = $args();
365            }
366
367            return $this->applyCalls($entry, $value(...$args), $context);
368        }
369
370        if (is_object($value)) {
371            return $value;
372        }
373
374        throw new NotFoundException('Unresolvable id: ' . (string) $value);
375    }
376
377    protected function applyCalls(Entry $entry, mixed $value, Container $context): mixed
378    {
379        foreach ($entry->getCalls() as $call) {
380            $methodToResolve = $call->method;
381
382            /** @var callable */
383            $callable = [$value, $methodToResolve];
384            $args = new CallableResolver($context->creator)->resolve($callable, $call->args);
385            $callable(...$args);
386        }
387
388        return $value;
389    }
390
391    /**
392     * @param array<int, true> $resetIds
393     */
394    protected function resetScope(array &$resetIds): void
395    {
396        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
397        /** @psalm-suppress MixedAssignment */
398        foreach ($this->instances as $instance) {
399            $this->resetIfNeeded($instance, $resetIds);
400        }
401
402        foreach ($this->usedResettables as $usedResettable) {
403            $this->resetIfNeeded($usedResettable, $resetIds);
404        }
405
406        foreach ($this->tags as $tagContainer) {
407            if (!$tagContainer->isScope) {
408                continue;
409            }
410
411            $tagContainer->resetScope($resetIds);
412        }
413
414        $this->instances = [];
415        $this->entries = [];
416        $this->tags = [];
417        $this->usedResettables = [];
418        $this->add(PsrContainer::class, $this);
419        $this->add(Container::class, $this);
420    }
421
422    /**
423     * @param array<int, true> $resetIds
424     */
425    protected function resetIfNeeded(mixed $value, array &$resetIds): void
426    {
427        if (!$value instanceof Resettable) {
428            return;
429        }
430
431        $objectId = spl_object_id($value);
432
433        if (isset($resetIds[$objectId])) {
434            return;
435        }
436
437        $resetIds[$objectId] = true;
438        $value->reset();
439    }
440
441    protected function trackAndReturn(mixed $value): mixed
442    {
443        if ($this->isScope && $value instanceof Resettable) {
444            $this->usedResettables[spl_object_id($value)] = $value;
445        }
446
447        return $value;
448    }
449
450    /** @return array{Container, Entry}|null */
451    protected function findEntry(string $id): ?array
452    {
453        $entry = $this->entries[$id] ?? null;
454
455        if ($entry !== null) {
456            return [$this, $entry];
457        }
458
459        return $this->parent?->findEntry($id);
460    }
461
462    /**
463     * The tag container holding the registrations this one inherits.
464     *
465     * A scope's tag container starts out empty â€” tag() links it to the root's
466     * container for the same tag, and the registrations live there. Only a
467     * same-tag parent counts: a tag container on a non-scope container has the
468     * owning container as its parent, and inheriting from that would list every
469     * service in the container under the tag.
470     */
471    protected function taggedParent(): ?Container
472    {
473        return $this->tag !== '' && $this->parent?->tag === $this->tag ? $this->parent : null;
474    }
475
476    protected function root(): Container
477    {
478        $container = $this;
479
480        while ($container->parent !== null) {
481            $container = $container->parent;
482        }
483
484        return $container;
485    }
486
487    protected function assertMutable(): void
488    {
489        if ($this->sealed) {
490            throw new ContainerException('The root container is sealed after scope() was called');
491        }
492    }
493
494    protected function seal(): void
495    {
496        $this->sealed = true;
497
498        foreach ($this->tags as $tagContainer) {
499            $tagContainer->seal();
500        }
501    }
502
503    protected function isRoot(): bool
504    {
505        return $this->parent === null && $this->tag === '' && !$this->isScope;
506    }
507}

Branches

Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

Container->__construct
39        public readonly bool $autowire = true,
40        ?PsrContainer $container = null,
41        protected readonly string $tag = '',
42        protected readonly ?Container $parent = null,
43        protected readonly bool $isScope = false,
44    ) {
45        if ($container) {
45        if ($container) {
46            $this->wrappedContainer = $container;
50            $this->wrappedContainer = null;
51            $this->add(PsrContainer::class, $this);
52        }
53        $this->add(Container::class, $this);
53        $this->add(Container::class, $this);
54        $this->creator = new Creator($this);
55    }
Container->add
192        string $id,
193        mixed $value = null,
194    ): Entry {
195        $this->assertMutable();
196        $entry = new Entry($id, $value ?? $id);
197        $this->entries[$id] = $entry;
198        unset($this->instances[$id]);
199
200        return $entry;
201    }
Container->addEntry
204        Entry $entry,
205    ): Entry {
206        $this->assertMutable();
207        $this->entries[$entry->id] = $entry;
208        unset($this->instances[$entry->id]);
209
210        return $entry;
211    }
Container->applyCalls
377    protected function applyCalls(Entry $entry, mixed $value, Container $context): mixed
378    {
379        foreach ($entry->getCalls() as $call) {
379        foreach ($entry->getCalls() as $call) {
379        foreach ($entry->getCalls() as $call) {
380            $methodToResolve = $call->method;
379        foreach ($entry->getCalls() as $call) {
380            $methodToResolve = $call->method;
381
382            /** @var callable */
383            $callable = [$value, $methodToResolve];
384            $args = new CallableResolver($context->creator)->resolve($callable, $call->args);
385            $callable(...$args);
386        }
387
388        return $value;
389    }
Container->assertMutable
489        if ($this->sealed) {
490            throw new ContainerException('The root container is sealed after scope() was called');
492    }
Container->definition
176    public function definition(string $id): mixed
177    {
178        $resolved = $this->findEntry($id);
179        $entry = $resolved[1] ?? null;
180
181        if ($entry !== null) {
182            return $entry->definition();
185        throw new NotFoundException('Unresolvable definition - id: ' . $id);
186    }
Container->entries
93    public function entries(bool $includeContainer = false): array
94    {
95        $inherited = $this->taggedParent()?->entries(true) ?? [];
96        $keys = $inherited === []
97            ? array_keys($this->entries)
96        $keys = $inherited === []
97            ? array_keys($this->entries)
98            : array_values(array_unique([...array_keys($this->entries), ...$inherited]));
96        $keys = $inherited === []
97            ? array_keys($this->entries)
98            : array_values(array_unique([...array_keys($this->entries), ...$inherited]));
99
100        if ($includeContainer) {
101            return $keys;
104        return array_values(array_filter(
105            $keys,
106            static fn($item) => $item !== PsrContainer::class && !is_subclass_of($item, PsrContainer::class),
107        ));
108    }
Container->entry
110    public function entry(string $id): Entry
111    {
112        $entry = $this->entries[$id] ?? null;
113
114        if ($entry !== null) {
115            return $entry;
118        $parent = $this->taggedParent();
119
120        if ($parent !== null) {
121            return $parent->entry($id);
124        throw new NotFoundException('Unresolvable entry - id: ' . $id);
125    }
Container->findEntry
451    protected function findEntry(string $id): ?array
452    {
453        $entry = $this->entries[$id] ?? null;
454
455        if ($entry !== null) {
456            return [$this, $entry];
459        return $this->parent?->findEntry($id);
460    }
Container->get
128    public function get(string $id): mixed
129    {
130        try {
131            if (array_key_exists($id, $this->instances)) {
132                return $this->trackAndReturn($this->instances[$id]);
135            $resolved = $this->findEntry($id);
136
137            if ($resolved !== null) {
138                return $this->trackAndReturn(
139                    $this->resolveEntry(
140                        entryOwner: $resolved[0],
148            $wrappedContainer = $this->root()->wrappedContainer;
149
150            if ($wrappedContainer?->has($id)) {
151                return $this->trackAndReturn($wrappedContainer->get($id));
155            if ($this->autowire && class_exists($id)) {
155            if ($this->autowire && class_exists($id)) {
155            if ($this->autowire && class_exists($id)) {
155            if ($this->autowire && class_exists($id)) {
155            if ($this->autowire && class_exists($id)) {
155            if ($this->autowire && class_exists($id)) {
156                return $this->trackAndReturn($this->creator->create($id));
156                return $this->trackAndReturn($this->creator->create($id));
158        } catch (WireException $e) {
159            throw new NotFoundException(
160                'Unresolvable id: ' . $id . ' - Details: ' . $e->getMessage(),
163        } catch (ContainerExceptionInterface $e) {
164            throw $e;
165        } catch (Throwable $e) {
166            throw new ContainerException(
167                'Unresolvable id: ' . $id . ' - ' . $e->getMessage(),
172        throw new NotFoundException('Unresolvable id: ' . $id);
173    }
Container->has
83    public function has(string $id): bool
84    {
85        return (
86            isset($this->entries[$id])
87                || $this->parent?->has($id)
87                || $this->parent?->has($id)
88                || $this->wrappedContainer?->has($id)
88                || $this->wrappedContainer?->has($id)
89        );
90    }
Container->isRoot
505        return $this->parent === null && $this->tag === '' && !$this->isScope;
505        return $this->parent === null && $this->tag === '' && !$this->isScope;
505        return $this->parent === null && $this->tag === '' && !$this->isScope;
505        return $this->parent === null && $this->tag === '' && !$this->isScope;
505        return $this->parent === null && $this->tag === '' && !$this->isScope;
506    }
Container->materialize
317    protected function materialize(Entry $entry, Container $context): mixed
318    {
319        /** @var mixed $value */
320        $value = $entry->definition();
321
322        if (is_string($value)) {
323            if (class_exists($value)) {
323            if (class_exists($value)) {
323            if (class_exists($value)) {
323            if (class_exists($value)) {
324                $constructor = $entry->getConstructor();
325                $args = $entry->getArgs();
326
327                if (isset($args)) {
329                    if ($args instanceof Closure) {
330                        $args = $args(...new CallableResolver($context->creator)->resolve($args));
331
332                        return $this->applyCalls($entry, $context->creator->create($value, $args), $context);
335                    return $this->applyCalls(
336                        $entry,
346                return $this->applyCalls(
347                    $entry,
353            if ($context->has($value)) {
354                return $context->get($value);
358        if ($value instanceof Closure) {
359            $args = $entry->getArgs();
360
361            if (is_null($args)) {
361            if (is_null($args)) {
362                $args = new CallableResolver($context->creator)->resolve($value);
363            } elseif ($args instanceof Closure) {
364                $args = $args();
365            }
366
367            return $this->applyCalls($entry, $value(...$args), $context);
367            return $this->applyCalls($entry, $value(...$args), $context);
370        if (is_object($value)) {
371            return $value;
374        throw new NotFoundException('Unresolvable id: ' . (string) $value);
375    }
Container->new
243    public function new(string $id, mixed ...$args): object
244    {
245        $entry = $this->entries[$id] ?? null;
246
247        if ($entry) {
249            $value = $entry->definition();
250
251            if (is_string($value)) {
252                if (interface_exists($value)) {
253                    return $this->new($value, ...$args);
256                if (class_exists($value)) {
256                if (class_exists($value)) {
256                if (class_exists($value)) {
256                if (class_exists($value)) {
258                    return new $value(...$args);
263        if (class_exists($id)) {
263        if (class_exists($id)) {
263        if (class_exists($id)) {
263        if (class_exists($id)) {
265            return new $id(...$args);
268        throw new NotFoundException('Cannot instantiate ' . $id);
269    }
Container->reset
74        if (!$this->isScope) {
75            return;
78        $resetIds = [];
79        $this->resetScope($resetIds);
80    }
Container->resetIfNeeded
425    protected function resetIfNeeded(mixed $value, array &$resetIds): void
426    {
427        if (!$value instanceof Resettable) {
428            return;
431        $objectId = spl_object_id($value);
432
433        if (isset($resetIds[$objectId])) {
434            return;
437        $resetIds[$objectId] = true;
438        $value->reset();
439    }
Container->resetScope
394    protected function resetScope(array &$resetIds): void
395    {
396        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
397        /** @psalm-suppress MixedAssignment */
398        foreach ($this->instances as $instance) {
398        foreach ($this->instances as $instance) {
398        foreach ($this->instances as $instance) {
399            $this->resetIfNeeded($instance, $resetIds);
398        foreach ($this->instances as $instance) {
399            $this->resetIfNeeded($instance, $resetIds);
400        }
401
402        foreach ($this->usedResettables as $usedResettable) {
402        foreach ($this->usedResettables as $usedResettable) {
402        foreach ($this->usedResettables as $usedResettable) {
403            $this->resetIfNeeded($usedResettable, $resetIds);
402        foreach ($this->usedResettables as $usedResettable) {
403            $this->resetIfNeeded($usedResettable, $resetIds);
404        }
405
406        foreach ($this->tags as $tagContainer) {
406        foreach ($this->tags as $tagContainer) {
407            if (!$tagContainer->isScope) {
408                continue;
406        foreach ($this->tags as $tagContainer) {
407            if (!$tagContainer->isScope) {
408                continue;
409            }
410
411            $tagContainer->resetScope($resetIds);
406        foreach ($this->tags as $tagContainer) {
407            if (!$tagContainer->isScope) {
408                continue;
409            }
410
411            $tagContainer->resetScope($resetIds);
412        }
413
414        $this->instances = [];
415        $this->entries = [];
416        $this->tags = [];
417        $this->usedResettables = [];
418        $this->add(PsrContainer::class, $this);
419        $this->add(Container::class, $this);
420    }
Container->resolutionContainers
306        Entry $entry,
307        Container $entryOwner,
308        Container $requester,
309    ): array {
310        return match ($entry->getLifetime()) {
311            Lifetime::Shared => [$entryOwner, $entryOwner],
312            Lifetime::Scoped => [$requester, $requester],
313            Lifetime::Transient => [null, $requester],
313            Lifetime::Transient => [null, $requester],
313            Lifetime::Transient => [null, $requester],
311            Lifetime::Shared => [$entryOwner, $entryOwner],
312            Lifetime::Scoped => [$requester, $requester],
313            Lifetime::Transient => [null, $requester],
313            Lifetime::Transient => [null, $requester],
314        };
315    }
Container->resolveEntry
272        Container $entryOwner,
273        Entry $entry,
274        string $id,
275        Container $requester,
276    ): mixed {
277        if ($entry->shouldReturnValue()) {
278            return $entry->definition();
281        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
282            $entry,
283            $entryOwner,
284            $requester,
285        );
286
287        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
287        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
287        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
288            return $cacheContainer->instances[$id];
293        $result = $this->materialize($entry, $resolutionContext);
294
295        if ($cacheContainer !== null) {
296            $cacheContainer->instances[$id] = $result;
297        }
298
299        return $result;
299        return $result;
300    }
Container->root
478        $container = $this;
479
480        while ($container->parent !== null) {
480        while ($container->parent !== null) {
481            $container = $container->parent;
480        while ($container->parent !== null) {
484        return $container;
485    }
Container->scope
59        $root = $this->root();
60
61        if (!$root->sealed) {
62            $root->seal();
63        }
64
65        return new self(
65        return new self(
66            autowire: $root->autowire,
67            parent: $root,
68            isScope: true,
69        );
70    }
Container->seal
496        $this->sealed = true;
497
498        foreach ($this->tags as $tagContainer) {
498        foreach ($this->tags as $tagContainer) {
498        foreach ($this->tags as $tagContainer) {
499            $tagContainer->seal();
498        foreach ($this->tags as $tagContainer) {
499            $tagContainer->seal();
500        }
501    }
Container->tag
214    public function tag(string $tag): Container
215    {
216        if (isset($this->tags[$tag])) {
217            return $this->tags[$tag];
220        if ($this->isRoot() && $this->sealed) {
220        if ($this->isRoot() && $this->sealed) {
220        if ($this->isRoot() && $this->sealed) {
221            throw new ContainerException('The root container is sealed after scope() was called');
224        $parent = $this;
225        $isScope = false;
226
227        if ($this->isScope) {
228            $root = $this->root();
229            $parent = $root->tags[$tag] ?? $root;
230            $isScope = true;
231        }
232
233        $this->tags[$tag] = new self(
233        $this->tags[$tag] = new self(
234            autowire: $this->autowire,
235            tag: $tag,
236            parent: $parent,
237            isScope: $isScope,
238        );
239
240        return $this->tags[$tag];
241    }
Container->taggedParent
473        return $this->tag !== '' && $this->parent?->tag === $this->tag ? $this->parent : null;
473        return $this->tag !== '' && $this->parent?->tag === $this->tag ? $this->parent : null;
473        return $this->tag !== '' && $this->parent?->tag === $this->tag ? $this->parent : null;
473        return $this->tag !== '' && $this->parent?->tag === $this->tag ? $this->parent : null;
473        return $this->tag !== '' && $this->parent?->tag === $this->tag ? $this->parent : null;
473        return $this->tag !== '' && $this->parent?->tag === $this->tag ? $this->parent : null;
474    }
Container->trackAndReturn
441    protected function trackAndReturn(mixed $value): mixed
442    {
443        if ($this->isScope && $value instanceof Resettable) {
443        if ($this->isScope && $value instanceof Resettable) {
443        if ($this->isScope && $value instanceof Resettable) {
444            $this->usedResettables[spl_object_id($value)] = $value;
445        }
446
447        return $value;
447        return $value;
448    }
{closure:/workspace/celema/container/src/Container.php:106-106}
106            static fn($item) => $item !== PsrContainer::class && !is_subclass_of($item, PsrContainer::class),
106            static fn($item) => $item !== PsrContainer::class && !is_subclass_of($item, PsrContainer::class),
106            static fn($item) => $item !== PsrContainer::class && !is_subclass_of($item, PsrContainer::class),