Passed
Push — psalm ( 5f77d0 )
by Sergei
03:54
created

CompositeContainer::attach()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 function get_class;
12
use function gettype;
13
use function is_object;
14
use function is_string;
15
16
/**
17
 * A composite container for use with containers that support the delegate lookup feature.
18
 */
19
final class CompositeContainer implements ContainerInterface
20
{
21
    /**
22
     * Containers to look into starting from the beginning of the array.
23
     *
24
     * @var ContainerInterface[] The list of containers.
25
     */
26
    private array $containers = [];
27
28
    /**
29
     * @param string $id
30
     *
31
     * @return mixed
32
     *
33
     * @psalm-template T
34
     * @psalm-param string|class-string<T> $id
35
     * @psalm-return ($id is class-string ? T : mixed)
36
     */
37 30
    public function get($id)
38
    {
39
        /** @psalm-suppress TypeDoesNotContainType */
40 30
        if (!is_string($id)) {
0 ignored issues
show
introduced by
The condition is_string($id) is always true.
Loading history...
41 1
            throw new InvalidArgumentException("Id must be a string, {$this->getVariableType($id)} given.");
42
        }
43
44 29
        if ($id === StateResetter::class) {
45 3
            $resetters = [];
46 3
            foreach ($this->containers as $container) {
47 3
                if ($container->has(StateResetter::class)) {
48 3
                    $resetters[] = $container->get(StateResetter::class);
49
                }
50
            }
51 3
            $stateResetter = new StateResetter($this);
52 3
            $stateResetter->setResetters($resetters);
53
54 3
            return $stateResetter;
55
        }
56
57 29
        if ($this->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
                    $tags = array_merge($container->get($id), $tags);
0 ignored issues
show
Bug introduced by
It seems like $container->get($id) can also be of type object; however, parameter $arrays of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

65
                    $tags = array_merge(/** @scrutinizer ignore-type */ $container->get($id), $tags);
Loading history...
66
                }
67
            }
68
69 3
            return $tags;
70
        }
71
72 26
        foreach ($this->containers as $container) {
73 26
            if ($container->has($id)) {
74
                /** @psalm-suppress MixedReturnStatement */
75 21
                return $container->get($id);
76
            }
77
        }
78
79
        // Collect details from containers
80 7
        $exceptions = [];
81 7
        foreach ($this->containers as $container) {
82 5
            $hasException = false;
83
            try {
84 5
                $container->get($id);
85 5
            } catch (Throwable $t) {
86 5
                $hasException = true;
87 5
                $exceptions[] = [$t, $container];
88 5
            } finally {
89 5
                if (!$hasException) {
90
                    $exceptions[] = [
91
                        new RuntimeException('Container has() returned false but no exception was thrown from get().'),
92 5
                        $container
93
                    ];
94
                }
95
            }
96
        }
97
98 7
        throw new CompositeNotFoundException($exceptions);
99
    }
100
101 23
    public function has($id): bool
102
    {
103 23
        foreach ($this->containers as $container) {
104 6
            if ($container->has($id)) {
105 6
                return true;
106
            }
107
        }
108 18
        return false;
109
    }
110
111
    /**
112
     * Attaches a container to the composite container.
113
     *
114
     * @param ContainerInterface $container
115
     */
116 31
    public function attach(ContainerInterface $container): void
117
    {
118 31
        $this->containers[] = $container;
119 31
    }
120
121
    /**
122
     * Removes a container from the list of containers.
123
     *
124
     * @param ContainerInterface $container
125
     */
126 2
    public function detach(ContainerInterface $container): void
127
    {
128 2
        foreach ($this->containers as $i => $c) {
129 2
            if ($container === $c) {
130 2
                unset($this->containers[$i]);
131
            }
132
        }
133 2
    }
134
135 29
    private function isTagAlias(string $id): bool
136
    {
137 29
        return strncmp($id, 'tag@', 4) === 0;
138
    }
139
140
    /**
141
     * @param mixed $variable
142
     */
143 1
    private function getVariableType($variable): string
144
    {
145 1
        return is_object($variable) ? get_class($variable) : gettype($variable);
146
    }
147
}
148