CompositeContainer   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 112
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 48
c 0
b 0
f 0
dl 0
loc 112
ccs 53
cts 53
cp 1
rs 10
wmc 21

4 Methods

Rating   Name   Duplication   Size   Complexity  
A attach() 0 3 1
A has() 0 8 3
A detach() 0 5 3
C get() 0 70 14
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Di;
6
7
use InvalidArgumentException;
8
use Psr\Container\ContainerInterface;
9
use RuntimeException;
10
use Throwable;
11
use Yiisoft\Di\Reference\TagReference;
12
13
use function is_string;
14
15
/**
16
 * A composite container for use with containers that support the delegate lookup feature.
17
 */
18
final class CompositeContainer implements ContainerInterface
19
{
20
    /**
21
     * Containers to look into starting from the beginning of the array.
22
     *
23
     * @var ContainerInterface[] The list of containers.
24
     */
25
    private array $containers = [];
26
27
    /**
28
     * @psalm-template T
29
     * @psalm-param string|class-string<T> $id
30
     * @psalm-return ($id is class-string ? T : mixed)
31
     */
32 34
    public function get($id)
33
    {
34
        /** @psalm-suppress TypeDoesNotContainType */
35 34
        if (!is_string($id)) {
36 1
            throw new InvalidArgumentException(
37 1
                sprintf(
38 1
                    'ID must be a string, %s given.',
39 1
                    get_debug_type($id)
40 1
                )
41 1
            );
42
        }
43
44 33
        if ($id === StateResetter::class) {
45 4
            $resetters = [];
46 4
            foreach ($this->containers as $container) {
47 4
                if ($container->has(StateResetter::class)) {
48 4
                    $resetters[] = $container->get(StateResetter::class);
49
                }
50
            }
51 4
            $stateResetter = new StateResetter($this);
52 4
            $stateResetter->setResetters($resetters);
53
54 4
            return $stateResetter;
55
        }
56
57 33
        if (TagReference::isTagAlias($id)) {
58 3
            $tags = [];
59 3
            foreach ($this->containers as $container) {
60 3
                if (!$container instanceof Container) {
61 1
                    continue;
62
                }
63 3
                if ($container->has($id)) {
64
                    /** @psalm-suppress MixedArgument `Container::get()` always return array for tag */
65 3
                    array_unshift($tags, $container->get($id));
66
                }
67
            }
68
69
            /** @psalm-suppress MixedArgument `Container::get()` always return array for tag */
70 3
            return array_merge(...$tags);
71
        }
72
73 30
        foreach ($this->containers as $container) {
74 30
            if ($container->has($id)) {
75
                /** @psalm-suppress MixedReturnStatement */
76 24
                return $container->get($id);
77
            }
78
        }
79
80
        // Collect details from containers
81 8
        $exceptions = [];
82 8
        foreach ($this->containers as $container) {
83 6
            $hasException = false;
84
            try {
85 6
                $container->get($id);
86 5
            } catch (Throwable $t) {
87 5
                $hasException = true;
88 5
                $exceptions[] = [$t, $container];
89
            } finally {
90 6
                if (!$hasException) {
91 6
                    $exceptions[] = [
92 6
                        new RuntimeException(
93 6
                            'Container "has()" returned false, but no exception was thrown from "get()".'
94 6
                        ),
95 6
                        $container,
96 6
                    ];
97
                }
98
            }
99
        }
100
101 8
        throw new CompositeNotFoundException($exceptions);
102
    }
103
104 35
    public function has($id): bool
105
    {
106 35
        foreach ($this->containers as $container) {
107 7
            if ($container->has($id)) {
108 7
                return true;
109
            }
110
        }
111 30
        return false;
112
    }
113
114
    /**
115
     * Attaches a container to the composite container.
116
     */
117 36
    public function attach(ContainerInterface $container): void
118
    {
119 36
        $this->containers[] = $container;
120
    }
121
122
    /**
123
     * Removes a container from the list of containers.
124
     */
125 2
    public function detach(ContainerInterface $container): void
126
    {
127 2
        foreach ($this->containers as $i => $c) {
128 2
            if ($container === $c) {
129 2
                unset($this->containers[$i]);
130
            }
131
        }
132
    }
133
}
134