Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
239 / 239
96.70% covered (success)
96.70%
176 / 182
47.22% covered (danger)
47.22%
85 / 180
83.33% covered (warning)
83.33%
20 / 24
CRAP
0.00% covered (danger)
0.00%
0 / 1
Container
100.00% covered (success)
100.00%
239 / 239
96.70% covered (success)
96.70%
176 / 182
47.22% covered (danger)
47.22%
85 / 180
100.00% covered (success)
100.00%
24 / 24
1336.31
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%
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
 has
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
7 / 7
37.50% covered (danger)
37.50%
3 / 8
100.00% covered (success)
100.00%
1 / 1
7.91
 entries
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 entry
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
 get
100.00% covered (success)
100.00%
32 / 32
96.00% covered (success)
96.00%
24 / 25
47.62% covered (danger)
47.62%
10 / 21
100.00% covered (success)
100.00%
1 / 1
28.39
 instance
100.00% covered (success)
100.00%
6 / 6
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%
15 / 15
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 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%
19 / 19
100.00% covered (success)
100.00%
15 / 15
24.14% covered (danger)
24.14%
7 / 29
100.00% covered (success)
100.00%
1 / 1
35.94
 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%
40 / 40
95.45% covered (success)
95.45%
21 / 22
66.67% covered (warning)
66.67%
10 / 15
100.00% covered (success)
100.00%
1 / 1
13.70
 checkedArgs
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
 applyCalls
100.00% covered (success)
100.00%
9 / 9
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
 resetScope
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
10 / 10
18.52% covered (danger)
18.52%
5 / 27
100.00% covered (success)
100.00%
1 / 1
12.66
 resetIfNeeded
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 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%
7 / 7
100.00% covered (success)
100.00%
10 / 10
71.43% covered (warning)
71.43%
5 / 7
100.00% covered (success)
100.00%
1 / 1
5.58
 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%
6 / 6
100.00% covered (success)
100.00%
9 / 9
16.67% covered (danger)
16.67%
2 / 12
100.00% covered (success)
100.00%
1 / 1
13.26
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Container;
6
7use Celema\Container\Exception\ContainerException;
8use Celema\Container\Exception\NotFoundException;
9use Celema\Container\Exception\ResetFailed;
10use Celema\Wire\CallableResolver;
11use Celema\Wire\Creator;
12use Celema\Wire\Exception\WireException;
13use Closure;
14use Override;
15use Psr\Container\ContainerExceptionInterface;
16use Psr\Container\ContainerInterface as PsrContainer;
17use Throwable;
18
19/** @psalm-api */
20class Container implements PsrContainer
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    /** The root tag whose registrations a scope's tag container lists and resolves. */
39    protected ?Container $inherits = null;
40
41    public function __construct(
42        public readonly bool $autowire = true,
43        ?PsrContainer $container = null,
44        protected readonly string $tag = '',
45        protected readonly ?Container $parent = null,
46        protected readonly bool $isScope = false,
47    ) {
48        if ($container) {
49            $this->wrappedContainer = $container;
50            $this->add(PsrContainer::class, $container);
51            $this->add($container::class, $container);
52        } else {
53            $this->wrappedContainer = null;
54            $this->add(PsrContainer::class, $this);
55        }
56        $this->add(Container::class, $this);
57        $this->creator = new Creator($this);
58    }
59
60    public function scope(): Container
61    {
62        $root = $this->root();
63
64        if (!$root->sealed) {
65            $root->seal();
66        }
67
68        return new self(
69            autowire: $root->autowire,
70            parent: $root,
71            isScope: true,
72        );
73    }
74
75    public function reset(): void
76    {
77        if (!$this->isScope) {
78            return;
79        }
80
81        $resetIds = [];
82        $failures = [];
83        $this->resetScope($resetIds, $failures);
84
85        if ($failures !== []) {
86            throw new ResetFailed($failures);
87        }
88    }
89
90    #[Override]
91    public function has(string $id): bool
92    {
93        return (
94            isset($this->entries[$id])
95                || isset($this->inherits?->entries[$id])
96                || $this->parent?->has($id)
97                || $this->wrappedContainer?->has($id)
98        );
99    }
100
101    /** @return list<string> */
102    public function entries(bool $includeContainer = false): array
103    {
104        $inherited = $this->inherits?->entries(true) ?? [];
105        $keys = $inherited === []
106            ? array_keys($this->entries)
107            : array_values(array_unique([...array_keys($this->entries), ...$inherited]));
108
109        if ($includeContainer) {
110            return $keys;
111        }
112
113        return array_values(array_filter(
114            $keys,
115            static fn(string $item): bool => $item !== PsrContainer::class
116            && !is_subclass_of($item, PsrContainer::class),
117        ));
118    }
119
120    public function entry(string $id): Entry
121    {
122        return (
123            $this->entries[$id]
124                ?? $this->inherits?->entries[$id]
125                ?? throw new NotFoundException('Unresolvable entry - id: ' . $id)
126        );
127    }
128
129    #[Override]
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
134                return $this->trackAndReturn($this->instances[$id]);
135            }
136
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
140                return $this->trackAndReturn(
141                    $this->resolveEntry(
142                        entryOwner: $resolved[0],
143                        entry: $resolved[1],
144                        id: $id,
145                        requester: $this,
146                    ),
147                );
148            }
149
150            if ($this->tag !== '' && $this->parent !== null) {
151                // Ids that were not tagged belong to the container that owns the
152                // tag. Resolving them there keeps their lifetime caches and the
153                // overrides of an enclosing scope.
154                return $this->parent->get($id);
155            }
156
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
160                return $this->trackAndReturn($wrappedContainer->get($id));
161            }
162
163            // Autowiring: $id does not exists as an entry in the container
164            if ($this->autowire && class_exists($id)) {
165                return $this->trackAndReturn($this->creator->create($id));
166            }
167        } catch (WireException $e) {
168            throw new NotFoundException(
169                'Unresolvable id: ' . $id . ' - Details: ' . $e->getMessage(),
170                previous: $e,
171            );
172        } catch (ContainerExceptionInterface $e) {
173            throw $e;
174        } catch (Throwable $e) {
175            throw new ContainerException(
176                'Unresolvable id: ' . $id . ' - ' . $e->getMessage(),
177                previous: $e,
178            );
179        }
180
181        throw new NotFoundException('Unresolvable id: ' . $id);
182    }
183
184    /**
185     * @template T of object
186     * @param class-string<T> $class
187     * @return T
188     */
189    public function instance(string $class): object
190    {
191        /** @var mixed $value */
192        $value = $this->get($class);
193
194        if (!$value instanceof $class) {
195            throw new ContainerException(
196                "Entry '{$class}' resolves to " . get_debug_type($value) . ', not an instance of it',
197            );
198        }
199
200        return $value;
201    }
202
203    /**
204     * @param non-empty-string $id
205     */
206    public function add(
207        string $id,
208        mixed $value = null,
209    ): Entry {
210        $this->assertMutable();
211        $entry = new Entry($id, $value ?? $id);
212        $this->entries[$id] = $entry;
213        unset($this->instances[$id]);
214
215        return $entry;
216    }
217
218    public function addEntry(
219        Entry $entry,
220    ): Entry {
221        $this->assertMutable();
222        $this->entries[$entry->id] = $entry;
223        unset($this->instances[$entry->id]);
224
225        return $entry;
226    }
227
228    /** @param non-empty-string $tag */
229    public function tag(string $tag): Container
230    {
231        if (isset($this->tags[$tag])) {
232            return $this->tags[$tag];
233        }
234
235        if ($this->sealed) {
236            // Reading a tag that was never registered is not a structural change.
237            // The empty view is not stored, so the sealed container stays as it is.
238            $view = new self(autowire: $this->autowire, tag: $tag, parent: $this);
239            $view->seal();
240
241            return $view;
242        }
243
244        $tagContainer = new self(
245            autowire: $this->autowire,
246            tag: $tag,
247            parent: $this,
248            isScope: $this->isScope,
249        );
250
251        // A scope's tag lists the root tag's registrations and adds its own,
252        // while untagged ids resolve through the scope.
253        if ($this->isScope) {
254            $tagContainer->inherits = $this->root()->tags[$tag] ?? null;
255        }
256
257        return $this->tags[$tag] = $tagContainer;
258    }
259
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
265            /** @var mixed $value */
266            $value = $entry->definition();
267
268            if (is_string($value)) {
269                if (interface_exists($value)) {
270                    return $this->new($value, ...$args);
271                }
272
273                if (class_exists($value)) {
274                    // Psalm cannot know the constructor of a class named at runtime.
275                    /** @psalm-suppress MixedMethodCall */
276                    return new $value(...$args);
277                }
278            }
279        }
280
281        if (class_exists($id)) {
282            // Psalm cannot know the constructor of a class named at runtime.
283            /** @psalm-suppress MixedMethodCall */
284            return new $id(...$args);
285        }
286
287        throw new NotFoundException('Cannot instantiate ' . $id);
288    }
289
290    protected function resolveEntry(
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
297            return $entry->definition();
298        }
299
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
301            throw new ContainerException(
302                "Scoped entry '{$id}' cannot be resolved by the root container after scope() was called, "
303                    . 'as every scope would share that instance. It is most likely a dependency of a shared '
304                    . 'entry: make that entry scoped or transient, or resolve the entry from a scope.',
305            );
306        }
307
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
315            return $cacheContainer->instances[$id];
316        }
317
318        // materialize() intentionally returns mixed because container entries may resolve to any value type.
319        /** @psalm-suppress MixedAssignment */
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
323            $cacheContainer->instances[$id] = $result;
324        }
325
326        return $result;
327    }
328
329    /**
330     * @return array{0: null|Container, 1: Container}
331     */
332    protected function resolutionContainers(
333        Entry $entry,
334        Container $entryOwner,
335        Container $requester,
336    ): array {
337        return match ($entry->getLifetime()) {
338            Lifetime::Shared => [$entryOwner, $entryOwner],
339            Lifetime::Scoped => [$requester, $requester],
340            Lifetime::Transient => [null, $requester],
341        };
342    }
343
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
350            if (class_exists($value)) {
351                $constructor = $entry->getConstructor();
352                $args = $entry->getArgs();
353
354                if (isset($args)) {
355                    // Don't autowire if $args are given
356                    if ($args instanceof Closure) {
357                        $args = self::checkedArgs(
358                            $entry,
359                            $args(...new CallableResolver($context->creator)->resolve($args)),
360                        );
361                    }
362
363                    return $this->applyCalls(
364                        $entry,
365                        $context->creator->create(
366                            $value,
367                            predefinedArgs: $args,
368                            constructor: $constructor ?? '',
369                        ),
370                        $context,
371                    );
372                }
373
374                return $this->applyCalls(
375                    $entry,
376                    $context->creator->create($value, constructor: $constructor ?? ''),
377                    $context,
378                );
379            }
380
381            if ($context->has($value)) {
382                return $context->get($value);
383            }
384
385            throw new NotFoundException('Unresolvable id: ' . $value);
386        }
387
388        if ($value instanceof Closure) {
389            $args = $entry->getArgs();
390
391            if (is_null($args)) {
392                $args = new CallableResolver($context->creator)->resolve($value);
393            } elseif ($args instanceof Closure) {
394                $args = self::checkedArgs($entry, $args());
395            }
396
397            return $this->applyCalls($entry, $value(...$args), $context);
398        }
399
400        if (is_object($value)) {
401            return $value;
402        }
403
404        throw new NotFoundException(
405            "Unresolvable id: {$entry->id} - cannot resolve a definition of type " . get_debug_type($value),
406        );
407    }
408
409    /** @return array<array-key, mixed> */
410    private static function checkedArgs(Entry $entry, mixed $args): array
411    {
412        if (!is_array($args)) {
413            throw new ContainerException(
414                "The args closure of entry '{$entry->id}' must return an array, " . get_debug_type($args) . ' returned',
415            );
416        }
417
418        return $args;
419    }
420
421    protected function applyCalls(Entry $entry, mixed $value, Container $context): mixed
422    {
423        foreach ($entry->getCalls() as $call) {
424            $callable = [$value, $call->method];
425
426            if (!is_callable($callable)) {
427                throw new ContainerException(
428                    "Entry '{$entry->id}' cannot call {$call->method}() on " . get_debug_type($value),
429                );
430            }
431
432            $args = new CallableResolver($context->creator)->resolve($callable, $call->args);
433            $callable(...$args);
434        }
435
436        return $value;
437    }
438
439    /**
440     * Attempts every reset hook and clears the scope even if hooks fail; the
441     * failures are collected for the caller.
442     *
443     * @param array<int, true> $resetIds
444     * @param list<Throwable> $failures
445     */
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
470
471    /**
472     * @param array<int, true> $resetIds
473     * @param list<Throwable> $failures
474     */
475    protected function resetIfNeeded(mixed $value, array &$resetIds, array &$failures): void
476    {
477        if (!$value instanceof Resettable) {
478            return;
479        }
480
481        $objectId = spl_object_id($value);
482
483        if (isset($resetIds[$objectId])) {
484            return;
485        }
486
487        $resetIds[$objectId] = true;
488
489        try {
490            $value->reset();
491        } catch (Throwable $e) {
492            $failures[] = $e;
493        }
494    }
495
496    protected function trackAndReturn(mixed $value): mixed
497    {
498        if ($this->isScope && $value instanceof Resettable) {
499            $this->usedResettables[spl_object_id($value)] = $value;
500        }
501
502        return $value;
503    }
504
505    /** @return array{Container, Entry}|null */
506    protected function findEntry(string $id): ?array
507    {
508        $entry = $this->entries[$id] ?? null;
509
510        if ($entry !== null) {
511            return [$this, $entry];
512        }
513
514        $inherits = $this->inherits;
515
516        if ($inherits !== null && isset($inherits->entries[$id])) {
517            // Inherited shared entries are cached by the root tag that defines them.
518            return [$inherits, $inherits->entries[$id]];
519        }
520
521        // A tag container only finds tagged entries; get() hands other ids to
522        // the container owning the tag.
523        return $this->tag === '' ? $this->parent?->findEntry($id) : null;
524    }
525
526    protected function root(): Container
527    {
528        $container = $this;
529
530        while ($container->parent !== null) {
531            $container = $container->parent;
532        }
533
534        return $container;
535    }
536
537    protected function assertMutable(): void
538    {
539        if ($this->sealed) {
540            throw new ContainerException('The root container is sealed after scope() was called');
541        }
542    }
543
544    protected function seal(): void
545    {
546        $this->sealed = true;
547
548        // Scoped entries resolved before the first scope must not outlive it:
549        // the sealed container rejects them from now on.
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
553            }
554        }
555
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
558        }
559    }
560}

Paths

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

Container->__construct
42        public readonly bool $autowire = true,
43        ?PsrContainer $container = null,
44        protected readonly string $tag = '',
45        protected readonly ?Container $parent = null,
46        protected readonly bool $isScope = false,
47    ) {
48        if ($container) {
 
48        if ($container) {
49            $this->wrappedContainer = $container;
 
56        $this->add(Container::class, $this);
57        $this->creator = new Creator($this);
58    }
42        public readonly bool $autowire = true,
43        ?PsrContainer $container = null,
44        protected readonly string $tag = '',
45        protected readonly ?Container $parent = null,
46        protected readonly bool $isScope = false,
47    ) {
48        if ($container) {
 
53            $this->wrappedContainer = null;
54            $this->add(PsrContainer::class, $this);
55        }
56        $this->add(Container::class, $this);
 
56        $this->add(Container::class, $this);
57        $this->creator = new Creator($this);
58    }
Container->add
207        string $id,
208        mixed $value = null,
209    ): Entry {
210        $this->assertMutable();
211        $entry = new Entry($id, $value ?? $id);
212        $this->entries[$id] = $entry;
213        unset($this->instances[$id]);
214
215        return $entry;
216    }
Container->addEntry
219        Entry $entry,
220    ): Entry {
221        $this->assertMutable();
222        $this->entries[$entry->id] = $entry;
223        unset($this->instances[$entry->id]);
224
225        return $entry;
226    }
Container->applyCalls
421    protected function applyCalls(Entry $entry, mixed $value, Container $context): mixed
422    {
423        foreach ($entry->getCalls() as $call) {
 
423        foreach ($entry->getCalls() as $call) {
 
424            $callable = [$value, $call->method];
425
426            if (!is_callable($callable)) {
 
427                throw new ContainerException(
428                    "Entry '{$entry->id}' cannot call {$call->method}() on " . get_debug_type($value),
421    protected function applyCalls(Entry $entry, mixed $value, Container $context): mixed
422    {
423        foreach ($entry->getCalls() as $call) {
 
423        foreach ($entry->getCalls() as $call) {
 
424            $callable = [$value, $call->method];
425
426            if (!is_callable($callable)) {
 
423        foreach ($entry->getCalls() as $call) {
424            $callable = [$value, $call->method];
425
426            if (!is_callable($callable)) {
427                throw new ContainerException(
428                    "Entry '{$entry->id}' cannot call {$call->method}() on " . get_debug_type($value),
429                );
430            }
431
432            $args = new CallableResolver($context->creator)->resolve($callable, $call->args);
 
423        foreach ($entry->getCalls() as $call) {
 
423        foreach ($entry->getCalls() as $call) {
424            $callable = [$value, $call->method];
425
426            if (!is_callable($callable)) {
427                throw new ContainerException(
428                    "Entry '{$entry->id}' cannot call {$call->method}() on " . get_debug_type($value),
429                );
430            }
431
432            $args = new CallableResolver($context->creator)->resolve($callable, $call->args);
433            $callable(...$args);
434        }
435
436        return $value;
437    }
421    protected function applyCalls(Entry $entry, mixed $value, Container $context): mixed
422    {
423        foreach ($entry->getCalls() as $call) {
 
423        foreach ($entry->getCalls() as $call) {
 
423        foreach ($entry->getCalls() as $call) {
424            $callable = [$value, $call->method];
425
426            if (!is_callable($callable)) {
427                throw new ContainerException(
428                    "Entry '{$entry->id}' cannot call {$call->method}() on " . get_debug_type($value),
429                );
430            }
431
432            $args = new CallableResolver($context->creator)->resolve($callable, $call->args);
433            $callable(...$args);
434        }
435
436        return $value;
437    }
421    protected function applyCalls(Entry $entry, mixed $value, Container $context): mixed
422    {
423        foreach ($entry->getCalls() as $call) {
 
423        foreach ($entry->getCalls() as $call) {
424            $callable = [$value, $call->method];
425
426            if (!is_callable($callable)) {
427                throw new ContainerException(
428                    "Entry '{$entry->id}' cannot call {$call->method}() on " . get_debug_type($value),
429                );
430            }
431
432            $args = new CallableResolver($context->creator)->resolve($callable, $call->args);
433            $callable(...$args);
434        }
435
436        return $value;
437    }
Container->assertMutable
539        if ($this->sealed) {
 
540            throw new ContainerException('The root container is sealed after scope() was called');
539        if ($this->sealed) {
 
542    }
Container->checkedArgs
410    private static function checkedArgs(Entry $entry, mixed $args): array
411    {
412        if (!is_array($args)) {
 
413            throw new ContainerException(
414                "The args closure of entry '{$entry->id}' must return an array, " . get_debug_type($args) . ' returned',
410    private static function checkedArgs(Entry $entry, mixed $args): array
411    {
412        if (!is_array($args)) {
 
418        return $args;
419    }
Container->entries
102    public function entries(bool $includeContainer = false): array
103    {
104        $inherited = $this->inherits?->entries(true) ?? [];
105        $keys = $inherited === []
 
106            ? array_keys($this->entries)
 
105        $keys = $inherited === []
106            ? array_keys($this->entries)
107            : array_values(array_unique([...array_keys($this->entries), ...$inherited]));
108
109        if ($includeContainer) {
 
110            return $keys;
102    public function entries(bool $includeContainer = false): array
103    {
104        $inherited = $this->inherits?->entries(true) ?? [];
105        $keys = $inherited === []
 
106            ? array_keys($this->entries)
 
105        $keys = $inherited === []
106            ? array_keys($this->entries)
107            : array_values(array_unique([...array_keys($this->entries), ...$inherited]));
108
109        if ($includeContainer) {
 
113        return array_values(array_filter(
114            $keys,
115            static fn(string $item): bool => $item !== PsrContainer::class
116            && !is_subclass_of($item, PsrContainer::class),
117        ));
118    }
102    public function entries(bool $includeContainer = false): array
103    {
104        $inherited = $this->inherits?->entries(true) ?? [];
105        $keys = $inherited === []
 
105        $keys = $inherited === []
106            ? array_keys($this->entries)
107            : array_values(array_unique([...array_keys($this->entries), ...$inherited]));
 
105        $keys = $inherited === []
106            ? array_keys($this->entries)
107            : array_values(array_unique([...array_keys($this->entries), ...$inherited]));
108
109        if ($includeContainer) {
 
110            return $keys;
102    public function entries(bool $includeContainer = false): array
103    {
104        $inherited = $this->inherits?->entries(true) ?? [];
105        $keys = $inherited === []
 
105        $keys = $inherited === []
106            ? array_keys($this->entries)
107            : array_values(array_unique([...array_keys($this->entries), ...$inherited]));
 
105        $keys = $inherited === []
106            ? array_keys($this->entries)
107            : array_values(array_unique([...array_keys($this->entries), ...$inherited]));
108
109        if ($includeContainer) {
 
113        return array_values(array_filter(
114            $keys,
115            static fn(string $item): bool => $item !== PsrContainer::class
116            && !is_subclass_of($item, PsrContainer::class),
117        ));
118    }
Container->entry
120    public function entry(string $id): Entry
121    {
122        return (
123            $this->entries[$id]
124                ?? $this->inherits?->entries[$id]
125                ?? throw new NotFoundException('Unresolvable entry - id: ' . $id)
126        );
127    }
Container->findEntry
506    protected function findEntry(string $id): ?array
507    {
508        $entry = $this->entries[$id] ?? null;
509
510        if ($entry !== null) {
 
511            return [$this, $entry];
506    protected function findEntry(string $id): ?array
507    {
508        $entry = $this->entries[$id] ?? null;
509
510        if ($entry !== null) {
 
514        $inherits = $this->inherits;
515
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
518            return [$inherits, $inherits->entries[$id]];
506    protected function findEntry(string $id): ?array
507    {
508        $entry = $this->entries[$id] ?? null;
509
510        if ($entry !== null) {
 
514        $inherits = $this->inherits;
515
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
523        return $this->tag === '' ? $this->parent?->findEntry($id) : null;
 
523        return $this->tag === '' ? $this->parent?->findEntry($id) : null;
 
523        return $this->tag === '' ? $this->parent?->findEntry($id) : null;
524    }
506    protected function findEntry(string $id): ?array
507    {
508        $entry = $this->entries[$id] ?? null;
509
510        if ($entry !== null) {
 
514        $inherits = $this->inherits;
515
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
523        return $this->tag === '' ? $this->parent?->findEntry($id) : null;
 
523        return $this->tag === '' ? $this->parent?->findEntry($id) : null;
 
523        return $this->tag === '' ? $this->parent?->findEntry($id) : null;
524    }
506    protected function findEntry(string $id): ?array
507    {
508        $entry = $this->entries[$id] ?? null;
509
510        if ($entry !== null) {
 
514        $inherits = $this->inherits;
515
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
518            return [$inherits, $inherits->entries[$id]];
506    protected function findEntry(string $id): ?array
507    {
508        $entry = $this->entries[$id] ?? null;
509
510        if ($entry !== null) {
 
514        $inherits = $this->inherits;
515
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
523        return $this->tag === '' ? $this->parent?->findEntry($id) : null;
 
523        return $this->tag === '' ? $this->parent?->findEntry($id) : null;
 
523        return $this->tag === '' ? $this->parent?->findEntry($id) : null;
524    }
506    protected function findEntry(string $id): ?array
507    {
508        $entry = $this->entries[$id] ?? null;
509
510        if ($entry !== null) {
 
514        $inherits = $this->inherits;
515
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
516        if ($inherits !== null && isset($inherits->entries[$id])) {
 
523        return $this->tag === '' ? $this->parent?->findEntry($id) : null;
 
523        return $this->tag === '' ? $this->parent?->findEntry($id) : null;
 
523        return $this->tag === '' ? $this->parent?->findEntry($id) : null;
524    }
Container->get
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
134                return $this->trackAndReturn($this->instances[$id]);
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
140                return $this->trackAndReturn(
141                    $this->resolveEntry(
142                        entryOwner: $resolved[0],
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
154                return $this->parent->get($id);
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
 
160                return $this->trackAndReturn($wrappedContainer->get($id));
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
165                return $this->trackAndReturn($this->creator->create($id));
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
165                return $this->trackAndReturn($this->creator->create($id));
 
181        throw new NotFoundException('Unresolvable id: ' . $id);
182    }
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
165                return $this->trackAndReturn($this->creator->create($id));
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
165                return $this->trackAndReturn($this->creator->create($id));
 
181        throw new NotFoundException('Unresolvable id: ' . $id);
182    }
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
165                return $this->trackAndReturn($this->creator->create($id));
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
165                return $this->trackAndReturn($this->creator->create($id));
 
181        throw new NotFoundException('Unresolvable id: ' . $id);
182    }
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
154                return $this->parent->get($id);
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
 
160                return $this->trackAndReturn($wrappedContainer->get($id));
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
165                return $this->trackAndReturn($this->creator->create($id));
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
165                return $this->trackAndReturn($this->creator->create($id));
 
181        throw new NotFoundException('Unresolvable id: ' . $id);
182    }
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
165                return $this->trackAndReturn($this->creator->create($id));
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
165                return $this->trackAndReturn($this->creator->create($id));
 
181        throw new NotFoundException('Unresolvable id: ' . $id);
182    }
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
165                return $this->trackAndReturn($this->creator->create($id));
130    public function get(string $id): mixed
131    {
132        try {
133            if (array_key_exists($id, $this->instances)) {
 
137            $resolved = $this->findEntry($id);
138
139            if ($resolved !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
150            if ($this->tag !== '' && $this->parent !== null) {
 
157            $wrappedContainer = $this->root()->wrappedContainer;
158
159            if ($wrappedContainer?->has($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
164            if ($this->autowire && class_exists($id)) {
 
165                return $this->trackAndReturn($this->creator->create($id));
 
181        throw new NotFoundException('Unresolvable id: ' . $id);
182    }
167        } catch (WireException $e) {
 
168            throw new NotFoundException(
169                'Unresolvable id: ' . $id . ' - Details: ' . $e->getMessage(),
167        } catch (WireException $e) {
 
172        } catch (ContainerExceptionInterface $e) {
 
173            throw $e;
167        } catch (WireException $e) {
 
172        } catch (ContainerExceptionInterface $e) {
 
174        } catch (Throwable $e) {
 
175            throw new ContainerException(
176                'Unresolvable id: ' . $id . ' - ' . $e->getMessage(),
Container->has
91    public function has(string $id): bool
92    {
93        return (
94            isset($this->entries[$id])
 
95                || isset($this->inherits?->entries[$id])
 
95                || isset($this->inherits?->entries[$id])
 
96                || $this->parent?->has($id)
 
96                || $this->parent?->has($id)
 
97                || $this->wrappedContainer?->has($id)
 
97                || $this->wrappedContainer?->has($id)
98        );
99    }
91    public function has(string $id): bool
92    {
93        return (
94            isset($this->entries[$id])
 
95                || isset($this->inherits?->entries[$id])
 
95                || isset($this->inherits?->entries[$id])
 
96                || $this->parent?->has($id)
 
96                || $this->parent?->has($id)
 
97                || $this->wrappedContainer?->has($id)
98        );
99    }
91    public function has(string $id): bool
92    {
93        return (
94            isset($this->entries[$id])
 
95                || isset($this->inherits?->entries[$id])
 
95                || isset($this->inherits?->entries[$id])
 
96                || $this->parent?->has($id)
 
97                || $this->wrappedContainer?->has($id)
 
97                || $this->wrappedContainer?->has($id)
98        );
99    }
91    public function has(string $id): bool
92    {
93        return (
94            isset($this->entries[$id])
 
95                || isset($this->inherits?->entries[$id])
 
95                || isset($this->inherits?->entries[$id])
 
96                || $this->parent?->has($id)
 
97                || $this->wrappedContainer?->has($id)
98        );
99    }
91    public function has(string $id): bool
92    {
93        return (
94            isset($this->entries[$id])
 
95                || isset($this->inherits?->entries[$id])
 
96                || $this->parent?->has($id)
 
96                || $this->parent?->has($id)
 
97                || $this->wrappedContainer?->has($id)
 
97                || $this->wrappedContainer?->has($id)
98        );
99    }
91    public function has(string $id): bool
92    {
93        return (
94            isset($this->entries[$id])
 
95                || isset($this->inherits?->entries[$id])
 
96                || $this->parent?->has($id)
 
96                || $this->parent?->has($id)
 
97                || $this->wrappedContainer?->has($id)
98        );
99    }
91    public function has(string $id): bool
92    {
93        return (
94            isset($this->entries[$id])
 
95                || isset($this->inherits?->entries[$id])
 
96                || $this->parent?->has($id)
 
97                || $this->wrappedContainer?->has($id)
 
97                || $this->wrappedContainer?->has($id)
98        );
99    }
91    public function has(string $id): bool
92    {
93        return (
94            isset($this->entries[$id])
 
95                || isset($this->inherits?->entries[$id])
 
96                || $this->parent?->has($id)
 
97                || $this->wrappedContainer?->has($id)
98        );
99    }
Container->instance
189    public function instance(string $class): object
190    {
191        /** @var mixed $value */
192        $value = $this->get($class);
193
194        if (!$value instanceof $class) {
 
195            throw new ContainerException(
196                "Entry '{$class}' resolves to " . get_debug_type($value) . ', not an instance of it',
189    public function instance(string $class): object
190    {
191        /** @var mixed $value */
192        $value = $this->get($class);
193
194        if (!$value instanceof $class) {
 
200        return $value;
201    }
Container->materialize
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
351                $constructor = $entry->getConstructor();
352                $args = $entry->getArgs();
353
354                if (isset($args)) {
 
356                    if ($args instanceof Closure) {
 
357                        $args = self::checkedArgs(
358                            $entry,
359                            $args(...new CallableResolver($context->creator)->resolve($args)),
360                        );
361                    }
362
363                    return $this->applyCalls(
 
363                    return $this->applyCalls(
364                        $entry,
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
351                $constructor = $entry->getConstructor();
352                $args = $entry->getArgs();
353
354                if (isset($args)) {
 
356                    if ($args instanceof Closure) {
 
363                    return $this->applyCalls(
364                        $entry,
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
351                $constructor = $entry->getConstructor();
352                $args = $entry->getArgs();
353
354                if (isset($args)) {
 
374                return $this->applyCalls(
375                    $entry,
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
381            if ($context->has($value)) {
 
382                return $context->get($value);
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
381            if ($context->has($value)) {
 
385            throw new NotFoundException('Unresolvable id: ' . $value);
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
351                $constructor = $entry->getConstructor();
352                $args = $entry->getArgs();
353
354                if (isset($args)) {
 
356                    if ($args instanceof Closure) {
 
357                        $args = self::checkedArgs(
358                            $entry,
359                            $args(...new CallableResolver($context->creator)->resolve($args)),
360                        );
361                    }
362
363                    return $this->applyCalls(
 
363                    return $this->applyCalls(
364                        $entry,
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
351                $constructor = $entry->getConstructor();
352                $args = $entry->getArgs();
353
354                if (isset($args)) {
 
356                    if ($args instanceof Closure) {
 
363                    return $this->applyCalls(
364                        $entry,
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
351                $constructor = $entry->getConstructor();
352                $args = $entry->getArgs();
353
354                if (isset($args)) {
 
374                return $this->applyCalls(
375                    $entry,
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
381            if ($context->has($value)) {
 
382                return $context->get($value);
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
350            if (class_exists($value)) {
 
381            if ($context->has($value)) {
 
385            throw new NotFoundException('Unresolvable id: ' . $value);
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
388        if ($value instanceof Closure) {
 
389            $args = $entry->getArgs();
390
391            if (is_null($args)) {
 
391            if (is_null($args)) {
392                $args = new CallableResolver($context->creator)->resolve($value);
 
397            return $this->applyCalls($entry, $value(...$args), $context);
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
388        if ($value instanceof Closure) {
 
389            $args = $entry->getArgs();
390
391            if (is_null($args)) {
 
393            } elseif ($args instanceof Closure) {
 
394                $args = self::checkedArgs($entry, $args());
395            }
396
397            return $this->applyCalls($entry, $value(...$args), $context);
 
397            return $this->applyCalls($entry, $value(...$args), $context);
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
388        if ($value instanceof Closure) {
 
389            $args = $entry->getArgs();
390
391            if (is_null($args)) {
 
393            } elseif ($args instanceof Closure) {
 
397            return $this->applyCalls($entry, $value(...$args), $context);
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
388        if ($value instanceof Closure) {
 
400        if (is_object($value)) {
 
401            return $value;
344    protected function materialize(Entry $entry, Container $context): mixed
345    {
346        /** @var mixed $value */
347        $value = $entry->definition();
348
349        if (is_string($value)) {
 
388        if ($value instanceof Closure) {
 
400        if (is_object($value)) {
 
404        throw new NotFoundException(
405            "Unresolvable id: {$entry->id} - cannot resolve a definition of type " . get_debug_type($value),
406        );
407    }
Container->new
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
269                if (interface_exists($value)) {
 
270                    return $this->new($value, ...$args);
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
269                if (interface_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
276                    return new $value(...$args);
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
269                if (interface_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
284            return new $id(...$args);
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
269                if (interface_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
287        throw new NotFoundException('Cannot instantiate ' . $id);
288    }
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
269                if (interface_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
284            return new $id(...$args);
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
269                if (interface_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
287        throw new NotFoundException('Cannot instantiate ' . $id);
288    }
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
269                if (interface_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
276                    return new $value(...$args);
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
269                if (interface_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
284            return new $id(...$args);
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
269                if (interface_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
287        throw new NotFoundException('Cannot instantiate ' . $id);
288    }
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
269                if (interface_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
284            return new $id(...$args);
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
269                if (interface_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
273                if (class_exists($value)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
287        throw new NotFoundException('Cannot instantiate ' . $id);
288    }
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
284            return new $id(...$args);
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
287        throw new NotFoundException('Cannot instantiate ' . $id);
288    }
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
284            return new $id(...$args);
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
266            $value = $entry->definition();
267
268            if (is_string($value)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
287        throw new NotFoundException('Cannot instantiate ' . $id);
288    }
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
284            return new $id(...$args);
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
287        throw new NotFoundException('Cannot instantiate ' . $id);
288    }
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
284            return new $id(...$args);
260    public function new(string $id, mixed ...$args): object
261    {
262        $entry = $this->entries[$id] ?? null;
263
264        if ($entry) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
281        if (class_exists($id)) {
 
287        throw new NotFoundException('Cannot instantiate ' . $id);
288    }
Container->reset
77        if (!$this->isScope) {
 
78            return;
77        if (!$this->isScope) {
 
81        $resetIds = [];
82        $failures = [];
83        $this->resetScope($resetIds, $failures);
84
85        if ($failures !== []) {
 
86            throw new ResetFailed($failures);
77        if (!$this->isScope) {
 
81        $resetIds = [];
82        $failures = [];
83        $this->resetScope($resetIds, $failures);
84
85        if ($failures !== []) {
 
88    }
Container->resetIfNeeded
475    protected function resetIfNeeded(mixed $value, array &$resetIds, array &$failures): void
476    {
477        if (!$value instanceof Resettable) {
 
478            return;
475    protected function resetIfNeeded(mixed $value, array &$resetIds, array &$failures): void
476    {
477        if (!$value instanceof Resettable) {
 
481        $objectId = spl_object_id($value);
482
483        if (isset($resetIds[$objectId])) {
 
484            return;
475    protected function resetIfNeeded(mixed $value, array &$resetIds, array &$failures): void
476    {
477        if (!$value instanceof Resettable) {
 
481        $objectId = spl_object_id($value);
482
483        if (isset($resetIds[$objectId])) {
 
487        $resetIds[$objectId] = true;
488
489        try {
490            $value->reset();
 
494    }
491        } catch (Throwable $e) {
 
492            $failures[] = $e;
493        }
494    }
 
494    }
Container->resetScope
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
446    protected function resetScope(array &$resetIds, array &$failures): void
447    {
448        // $instances stores mixed values by design, so foreach assigns mixed to $instance.
449        /** @psalm-suppress MixedAssignment */
450        foreach ($this->instances as $instance) {
 
450        foreach ($this->instances as $instance) {
451            $this->resetIfNeeded($instance, $resetIds, $failures);
452        }
453
454        foreach ($this->usedResettables as $usedResettable) {
 
454        foreach ($this->usedResettables as $usedResettable) {
455            $this->resetIfNeeded($usedResettable, $resetIds, $failures);
456        }
457
458        // A scope's tags inherit isScope from it, so all of them are scopes.
459        foreach ($this->tags as $tagContainer) {
 
459        foreach ($this->tags as $tagContainer) {
460            $tagContainer->resetScope($resetIds, $failures);
461        }
462
463        $this->instances = [];
464        $this->entries = [];
465        $this->tags = [];
466        $this->usedResettables = [];
467        $this->add(PsrContainer::class, $this);
468        $this->add(Container::class, $this);
469    }
Container->resolutionContainers
333        Entry $entry,
334        Container $entryOwner,
335        Container $requester,
336    ): array {
337        return match ($entry->getLifetime()) {
338            Lifetime::Shared => [$entryOwner, $entryOwner],
 
339            Lifetime::Scoped => [$requester, $requester],
 
340            Lifetime::Transient => [null, $requester],
 
340            Lifetime::Transient => [null, $requester],
 
340            Lifetime::Transient => [null, $requester],
333        Entry $entry,
334        Container $entryOwner,
335        Container $requester,
336    ): array {
337        return match ($entry->getLifetime()) {
338            Lifetime::Shared => [$entryOwner, $entryOwner],
 
339            Lifetime::Scoped => [$requester, $requester],
 
340            Lifetime::Transient => [null, $requester],
 
340            Lifetime::Transient => [null, $requester],
 
340            Lifetime::Transient => [null, $requester],
341        };
342    }
333        Entry $entry,
334        Container $entryOwner,
335        Container $requester,
336    ): array {
337        return match ($entry->getLifetime()) {
338            Lifetime::Shared => [$entryOwner, $entryOwner],
 
339            Lifetime::Scoped => [$requester, $requester],
 
339            Lifetime::Scoped => [$requester, $requester],
 
340            Lifetime::Transient => [null, $requester],
341        };
342    }
333        Entry $entry,
334        Container $entryOwner,
335        Container $requester,
336    ): array {
337        return match ($entry->getLifetime()) {
338            Lifetime::Shared => [$entryOwner, $entryOwner],
 
338            Lifetime::Shared => [$entryOwner, $entryOwner],
 
340            Lifetime::Transient => [null, $requester],
341        };
342    }
Container->resolveEntry
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
297            return $entry->definition();
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
301            throw new ContainerException(
302                "Scoped entry '{$id}' cannot be resolved by the root container after scope() was called, "
303                    . 'as every scope would share that instance. It is most likely a dependency of a shared '
304                    . 'entry: make that entry scoped or transient, or resolve the entry from a scope.',
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
315            return $cacheContainer->instances[$id];
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
323            $cacheContainer->instances[$id] = $result;
324        }
325
326        return $result;
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
315            return $cacheContainer->instances[$id];
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
323            $cacheContainer->instances[$id] = $result;
324        }
325
326        return $result;
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
301            throw new ContainerException(
302                "Scoped entry '{$id}' cannot be resolved by the root container after scope() was called, "
303                    . 'as every scope would share that instance. It is most likely a dependency of a shared '
304                    . 'entry: make that entry scoped or transient, or resolve the entry from a scope.',
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
315            return $cacheContainer->instances[$id];
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
323            $cacheContainer->instances[$id] = $result;
324        }
325
326        return $result;
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
315            return $cacheContainer->instances[$id];
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
323            $cacheContainer->instances[$id] = $result;
324        }
325
326        return $result;
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
301            throw new ContainerException(
302                "Scoped entry '{$id}' cannot be resolved by the root container after scope() was called, "
303                    . 'as every scope would share that instance. It is most likely a dependency of a shared '
304                    . 'entry: make that entry scoped or transient, or resolve the entry from a scope.',
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
315            return $cacheContainer->instances[$id];
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
323            $cacheContainer->instances[$id] = $result;
324        }
325
326        return $result;
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
315            return $cacheContainer->instances[$id];
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
323            $cacheContainer->instances[$id] = $result;
324        }
325
326        return $result;
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
301            throw new ContainerException(
302                "Scoped entry '{$id}' cannot be resolved by the root container after scope() was called, "
303                    . 'as every scope would share that instance. It is most likely a dependency of a shared '
304                    . 'entry: make that entry scoped or transient, or resolve the entry from a scope.',
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
315            return $cacheContainer->instances[$id];
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
323            $cacheContainer->instances[$id] = $result;
324        }
325
326        return $result;
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
315            return $cacheContainer->instances[$id];
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
323            $cacheContainer->instances[$id] = $result;
324        }
325
326        return $result;
 
326        return $result;
327    }
291        Container $entryOwner,
292        Entry $entry,
293        string $id,
294        Container $requester,
295    ): mixed {
296        if ($entry->shouldReturnValue()) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
300        if ($entry->getLifetime() === Lifetime::Scoped && !$requester->isScope && $requester->sealed) {
 
308        [$cacheContainer, $resolutionContext] = $this->resolutionContainers(
309            $entry,
310            $entryOwner,
311            $requester,
312        );
313
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
314        if ($cacheContainer !== null && array_key_exists($id, $cacheContainer->instances)) {
 
320        $result = $this->materialize($entry, $resolutionContext);
321
322        if ($cacheContainer !== null) {
 
326        return $result;
327    }
Container->root
528        $container = $this;
529
530        while ($container->parent !== null) {
 
530        while ($container->parent !== null) {
 
534        return $container;
535    }
528        $container = $this;
529
530        while ($container->parent !== null) {
 
530        while ($container->parent !== null) {
 
530        while ($container->parent !== null) {
531            $container = $container->parent;
 
530        while ($container->parent !== null) {
 
534        return $container;
535    }
Container->scope
62        $root = $this->root();
63
64        if (!$root->sealed) {
 
65            $root->seal();
66        }
67
68        return new self(
 
68        return new self(
69            autowire: $root->autowire,
70            parent: $root,
71            isScope: true,
72        );
73    }
62        $root = $this->root();
63
64        if (!$root->sealed) {
 
68        return new self(
69            autowire: $root->autowire,
70            parent: $root,
71            isScope: true,
72        );
73    }
Container->seal
546        $this->sealed = true;
547
548        // Scoped entries resolved before the first scope must not outlive it:
549        // the sealed container rejects them from now on.
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
553            }
554        }
555
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
 
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
558        }
559    }
546        $this->sealed = true;
547
548        // Scoped entries resolved before the first scope must not outlive it:
549        // the sealed container rejects them from now on.
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
553            }
554        }
555
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
558        }
559    }
546        $this->sealed = true;
547
548        // Scoped entries resolved before the first scope must not outlive it:
549        // the sealed container rejects them from now on.
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
553            }
554        }
555
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
558        }
559    }
546        $this->sealed = true;
547
548        // Scoped entries resolved before the first scope must not outlive it:
549        // the sealed container rejects them from now on.
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
553            }
554        }
555
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
 
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
558        }
559    }
546        $this->sealed = true;
547
548        // Scoped entries resolved before the first scope must not outlive it:
549        // the sealed container rejects them from now on.
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
553            }
554        }
555
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
558        }
559    }
546        $this->sealed = true;
547
548        // Scoped entries resolved before the first scope must not outlive it:
549        // the sealed container rejects them from now on.
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
553            }
554        }
555
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
558        }
559    }
546        $this->sealed = true;
547
548        // Scoped entries resolved before the first scope must not outlive it:
549        // the sealed container rejects them from now on.
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
553            }
554        }
555
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
 
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
558        }
559    }
546        $this->sealed = true;
547
548        // Scoped entries resolved before the first scope must not outlive it:
549        // the sealed container rejects them from now on.
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
553            }
554        }
555
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
558        }
559    }
546        $this->sealed = true;
547
548        // Scoped entries resolved before the first scope must not outlive it:
549        // the sealed container rejects them from now on.
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
553            }
554        }
555
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
558        }
559    }
546        $this->sealed = true;
547
548        // Scoped entries resolved before the first scope must not outlive it:
549        // the sealed container rejects them from now on.
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
553            }
554        }
555
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
 
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
558        }
559    }
546        $this->sealed = true;
547
548        // Scoped entries resolved before the first scope must not outlive it:
549        // the sealed container rejects them from now on.
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
553            }
554        }
555
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
558        }
559    }
546        $this->sealed = true;
547
548        // Scoped entries resolved before the first scope must not outlive it:
549        // the sealed container rejects them from now on.
550        foreach ($this->entries as $id => $entry) {
 
550        foreach ($this->entries as $id => $entry) {
551            if ($entry->getLifetime() === Lifetime::Scoped) {
552                unset($this->instances[$id]);
553            }
554        }
555
556        foreach ($this->tags as $tagContainer) {
 
556        foreach ($this->tags as $tagContainer) {
557            $tagContainer->seal();
558        }
559    }
Container->tag
229    public function tag(string $tag): Container
230    {
231        if (isset($this->tags[$tag])) {
 
232            return $this->tags[$tag];
229    public function tag(string $tag): Container
230    {
231        if (isset($this->tags[$tag])) {
 
235        if ($this->sealed) {
 
238            $view = new self(autowire: $this->autowire, tag: $tag, parent: $this);
239            $view->seal();
240
241            return $view;
229    public function tag(string $tag): Container
230    {
231        if (isset($this->tags[$tag])) {
 
235        if ($this->sealed) {
 
244        $tagContainer = new self(
245            autowire: $this->autowire,
246            tag: $tag,
247            parent: $this,
248            isScope: $this->isScope,
249        );
250
251        // A scope's tag lists the root tag's registrations and adds its own,
252        // while untagged ids resolve through the scope.
253        if ($this->isScope) {
 
254            $tagContainer->inherits = $this->root()->tags[$tag] ?? null;
255        }
256
257        return $this->tags[$tag] = $tagContainer;
 
257        return $this->tags[$tag] = $tagContainer;
258    }
229    public function tag(string $tag): Container
230    {
231        if (isset($this->tags[$tag])) {
 
235        if ($this->sealed) {
 
244        $tagContainer = new self(
245            autowire: $this->autowire,
246            tag: $tag,
247            parent: $this,
248            isScope: $this->isScope,
249        );
250
251        // A scope's tag lists the root tag's registrations and adds its own,
252        // while untagged ids resolve through the scope.
253        if ($this->isScope) {
 
257        return $this->tags[$tag] = $tagContainer;
258    }
Container->trackAndReturn
496    protected function trackAndReturn(mixed $value): mixed
497    {
498        if ($this->isScope && $value instanceof Resettable) {
 
498        if ($this->isScope && $value instanceof Resettable) {
 
498        if ($this->isScope && $value instanceof Resettable) {
 
499            $this->usedResettables[spl_object_id($value)] = $value;
500        }
501
502        return $value;
 
502        return $value;
503    }
496    protected function trackAndReturn(mixed $value): mixed
497    {
498        if ($this->isScope && $value instanceof Resettable) {
 
498        if ($this->isScope && $value instanceof Resettable) {
 
498        if ($this->isScope && $value instanceof Resettable) {
 
502        return $value;
503    }
496    protected function trackAndReturn(mixed $value): mixed
497    {
498        if ($this->isScope && $value instanceof Resettable) {
 
498        if ($this->isScope && $value instanceof Resettable) {
 
499            $this->usedResettables[spl_object_id($value)] = $value;
500        }
501
502        return $value;
 
502        return $value;
503    }
496    protected function trackAndReturn(mixed $value): mixed
497    {
498        if ($this->isScope && $value instanceof Resettable) {
 
498        if ($this->isScope && $value instanceof Resettable) {
 
502        return $value;
503    }
{closure:/workspace/celema/container/src/Container.php:115-116}
115            static fn(string $item): bool => $item !== PsrContainer::class
 
116            && !is_subclass_of($item, PsrContainer::class),
 
116            && !is_subclass_of($item, PsrContainer::class),
115            static fn(string $item): bool => $item !== PsrContainer::class
 
116            && !is_subclass_of($item, PsrContainer::class),