Passed
Push — master ( 78cb0a...99c1b2 )
by Arnold
03:01
created

Container::findSubContainer()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 11
c 0
b 0
f 0
nc 6
nop 1
dl 0
loc 18
ccs 12
cts 12
cp 1
crap 4
rs 9.9
1
<?php declare(strict_types=1);
2
3
namespace Jasny\Container;
4
5
use Interop\Container\ContainerInterface as InteropContainer;
6
use Jasny\Container\Autowire\AutowireInterface;
7
use Psr\Container\ContainerInterface;
8
use Jasny\Container\Exception\NotFoundException;
9
use Jasny\Container\Exception\NoSubContainerException;
10
11
use function Jasny\expect_type;
12
13
/**
14
 * This class is a minimalist dependency injection container.
15
 * It has compatibility with container-interop's ContainerInterface and delegate-lookup feature.
16
 */
17
class Container implements InteropContainer, AutowireContainerInterface
18
{
19
    /**
20
     * The delegate lookup.
21
     *
22
     * @var ContainerInterface
23
     */
24
    protected $delegateLookupContainer;
25
26
    /**
27
     * The array of closures defining each entry of the container.
28
     *
29
     * @var \Closure[]
30
     */
31
    protected $callbacks;
32
33
    /**
34
     * The array of entries once they have been instantiated.
35
     *
36
     * @var mixed[]
37
     */
38
    protected $instances = [];
39
40
41
    /**
42
     * Class constructor
43
     *
44
     * @param \Traversable<\Closure>|\Closure[] $entries Entries must be passed as an array of anonymous functions.
45
     * @param ContainerInterface $delegateLookupContainer Optional delegate lookup container.
46
     */
47 14
    public function __construct(iterable $entries, ContainerInterface $delegateLookupContainer = null)
48
    {
49 14
        $this->callbacks = is_array($entries) ? $entries : iterator_to_array($entries);
50 14
        $this->delegateLookupContainer = $delegateLookupContainer ?: $this;
51 14
    }
52
53
54
    /**
55
     * Get an instance from the container.
56
     *
57
     * @param string $identifier
58
     * @return mixed
59
     * @throws NotFoundException
60
     * @throws NoSubContainerException
61
     */
62 13
    public function get($identifier)
63
    {
64 13
        expect_type($identifier, 'string');
65
66 13
        if (array_key_exists($identifier, $this->instances)) {
67 3
            return $this->instances[$identifier];
68
        }
69
70 13
        if (!isset($this->callbacks[$identifier])) {
71 4
            return $this->getSub($identifier);
72
        }
73
74 11
        $instance = $this->callbacks[$identifier]($this->delegateLookupContainer);
75 11
        $this->assertType($instance, $identifier);
76
77 9
        $this->instances[$identifier] = $instance;
78
79 9
        return $instance;
80
    }
81
82
    /**
83
     * Check if the container has an entry.
84
     *
85
     * @param string $identifier
86
     * @return bool
87
     * @throws NotFoundException
88
     * @throws NoSubContainerException
89
     */
90 3
    public function has($identifier): bool
91
    {
92 3
        expect_type($identifier, 'string');
93
94 3
        return isset($this->callbacks[$identifier]) || $this->hasSub($identifier);
95
    }
96
97
    /**
98
     * Instantiate a new object, autowire dependencies.
99
     *
100
     * @param string $class
101
     * @param mixed ...$args
102
     * @return object
103
     * @throws NotFoundException
104
     * @throws NoSubContainerException
105
     */
106 3
    public function autowire(string $class, ...$args)
107
    {
108 3
        return $this->get(AutowireInterface::class)->instantiate($class, ...$args);
109
    }
110
111
112
    /**
113
     * Check the type of the instance if the identifier is an interface or class name.
114
     *
115
     * @param mixed  $instance
116
     * @param string $identifier
117
     * @throws \TypeError
118
     */
119 11
    protected function assertType($instance, string $identifier): void
120
    {
121 11
        if (ctype_upper($identifier[0]) &&
122 11
            strpos($identifier, '.') === false &&
123 11
            (class_exists($identifier) || interface_exists($identifier)) &&
124 11
            !is_a($instance, $identifier)
125
        ) {
126 2
            $type = (is_object($instance) ? get_class($instance) . ' ' : '') . gettype($instance);
127 2
            throw new \TypeError("Entry is a $type, which does not implement $identifier");
128
        }
129 9
    }
130
131
132
    /**
133
     * Get an instance from a subcontainer.
134
     *
135
     * @param string $identifier
136
     * @return mixed
137
     * @throws NotFoundException
138
     * @throws NoSubContainerException
139
     */
140 4
    protected function getSub(string $identifier)
141
    {
142 4
        [$subcontainer, $containerId, $subId] = $this->findSubContainer($identifier);
143
144 4
        if (!isset($subcontainer)) {
145 2
            throw new NotFoundException("Entry \"$identifier\" is not defined.");
146
        }
147
148 2
        if (!$subcontainer instanceof ContainerInterface) {
149 1
            throw new NoSubContainerException("Entry \"$containerId\" is not a PSR-11 compatible container");
150
        }
151
152 1
        return $subcontainer->get($subId);
153
    }
154
155
    /**
156
     * Get an instance from a subcontainer.
157
     *
158
     * @param string $identifier
159
     * @return bool
160
     * @throws NotFoundException
161
     * @throws NoSubContainerException
162
     */
163 3
    protected function hasSub(string $identifier): bool
164
    {
165 3
        [$subcontainer, , $subId] = $this->findSubContainer($identifier);
166
167 3
        return isset($subcontainer) && $subcontainer instanceof ContainerInterface && $subcontainer->has($subId);
168
    }
169
170
    /**
171
     * Find an subcontainer iterating through the identifier parts.
172
     *
173
     * @param string $identifier
174
     * @return array  [subcontainer, container id, subidentifier]
175
     * @throws NotFoundException
176
     * @throws NoSubContainerException
177
     */
178 7
    protected function findSubContainer(string $identifier): array
179
    {
180 7
        $containerId = null;
181 7
        $subcontainer = null;
182 7
        $parts = explode('.', $identifier);
183 7
        $subParts = [];
184
185 7
        while ($parts !== []) {
186 7
            array_unshift($subParts, array_pop($parts));
187 7
            $containerId = join('.', $parts);
188
189 7
            if (isset($this->callbacks[$containerId])) {
190 4
                $subcontainer = $this->get($containerId);
191 4
                break;
192
            }
193
        }
194
195 7
        return isset($subcontainer) ? [$subcontainer, $containerId, join('.', $subParts)] : [null, null, null];
196
    }
197
}
198