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

CompositeContainer::get()   C

Complexity

Conditions 14
Paths 27

Size

Total Lines 62
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 14.0364

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 35
c 1
b 0
f 0
nc 27
nop 1
dl 0
loc 62
ccs 33
cts 35
cp 0.9429
crap 14.0364
rs 6.2666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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