Container   A
last analyzed

Complexity

Total Complexity 38

Size/Duplication

Total Lines 268
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 94.12%

Importance

Changes 0
Metric Value
wmc 38
lcom 1
cbo 2
dl 0
loc 268
ccs 80
cts 85
cp 0.9412
rs 9.36
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A bind() 0 10 3
A bindContext() 0 6 2
A instance() 0 4 1
A make() 0 14 3
A build() 0 22 5
A isBound() 0 4 2
B getDependencies() 0 40 8
A buildContextualBinding() 0 12 3
A getConcrete() 0 12 4
A offsetExists() 0 4 1
A offsetGet() 0 4 1
A offsetSet() 0 4 1
A offsetUnset() 0 4 1
A get() 0 8 2
A has() 0 4 1
1
<?php
2
/**
3
 * @author: RunnerLee
4
 * @email: [email protected]
5
 * @time: 2019-04
6
 */
7
8
namespace Runner\Container;
9
10
use ArrayAccess;
11
use Closure;
12
use Exception;
13
use Psr\Container\ContainerInterface;
14
use ReflectionClass;
15
use ReflectionException;
16
use ReflectionParameter;
17
use Runner\Container\Exceptions\BindingResolutionException;
18
use Runner\Container\Exceptions\EntryNotFoundException;
19
20
class Container implements ArrayAccess, ContainerInterface
21
{
22
    /**
23
     * @var array
24
     */
25
    protected $bindings = [];
26
27
    /**
28
     * @var array
29
     */
30
    protected $instances = [];
31
32
    /**
33
     * @var array
34
     */
35
    protected $shares = [];
36
37
    /**
38
     * @var array
39
     */
40
    protected $contextual = [];
41
42
    /**
43
     * @param string          $name
44
     * @param string|callable $concrete
45
     * @param bool            $share
46
     */
47 12
    public function bind($name, $concrete = null, $share = false)
48
    {
49 12
        if (is_null($concrete)) {
50 5
            $concrete = $name;
51
        }
52
53 12
        $this->bindings[$name] = $concrete;
54
55 12
        $share && $this->shares[$name] = true;
56 12
    }
57
58
    /**
59
     * @param $concretes
60
     * @param $parameter
61
     * @param $implementation
62
     */
63 2
    public function bindContext($concretes, $parameter, $implementation)
64
    {
65 2
        foreach ((array) $concretes as $concrete) {
66 2
            $this->contextual[$concrete][$parameter] = $implementation;
67
        }
68 2
    }
69
70
    /**
71
     * @param $name
72
     * @param $instance
73
     */
74 2
    public function instance($name, $instance)
75
    {
76 2
        $this->instances[$name] = $instance;
77 2
    }
78
79
    /**
80
     * @param string $name
81
     *
82
     * @throws ReflectionException
83
     *
84
     * @return object
85
     */
86 16
    public function make($name)
87
    {
88 16
        if (isset($this->instances[$name])) {
89 4
            return $this->instances[$name];
90
        }
91
92 14
        $instance = $this->build($name);
93
94 11
        if (isset($this->shares[$name])) {
95 2
            $this->instances[$name] = $instance;
96
        }
97
98 11
        return $instance;
99
    }
100
101
    /**
102
     * @param $name
103
     *
104
     * @throws ReflectionException
105
     * @throws BindingResolutionException
106
     *
107
     * @return mixed|object
108
     */
109 14
    protected function build($name)
110
    {
111 14
        $concrete = $this->getConcrete($name);
112
113 14
        if ($concrete instanceof Closure) {
114 7
            return $concrete($this);
115
        }
116
117 12
        $reflector = new ReflectionClass($concrete);
118
119 11
        if (!$reflector->isInstantiable()) {
120 2
            throw new BindingResolutionException(sprintf('%s is not instantiable', $name));
121
        }
122
123 10
        $constructor = $reflector->getConstructor();
124
125 10
        if (!$constructor || !$constructor->getParameters()) {
126 10
            return $reflector->newInstance();
127
        }
128
129 5
        return $reflector->newInstanceArgs($this->getDependencies($concrete, $constructor->getParameters()));
130
    }
131
132
    /**
133
     * @param $name
134
     *
135
     * @return bool
136
     */
137 4
    public function isBound($name)
138
    {
139 4
        return isset($this->instances[$name]) || isset($this->bindings[$name]);
140
    }
141
142
    /**
143
     * @param $concrete
144
     * @param ReflectionParameter[] $reflectionParameters
145
     *
146
     * @throws Exception
147
     *
148
     * @return array
149
     */
150 5
    protected function getDependencies($concrete, array $reflectionParameters)
151
    {
152 5
        $result = [];
153 5
        foreach ($reflectionParameters as $parameter) {
154 5
            if (!is_null($parameter->getClass())) {
155
                try {
156 5
                    $class = $parameter->getClass()->getName();
157
158 5
                    $result[] = isset($this->contextual[$concrete][$class])
159 2
                        ? $this->buildContextualBinding($this->contextual[$concrete][$class])
160 5
                        : $this->make($class);
161 2
                } catch (Exception $exception) {
162 2
                    if (!$parameter->isOptional()) {
163 1
                        throw $exception;
164
                    }
165 1
                    $result[] = $parameter->getDefaultValue();
166
                }
167 4
                continue;
168
            }
169
170 1
            if (isset($this->contextual[$concrete][$name = $parameter->getName()])) {
171
                $result[] = $this->buildContextualBinding(sprintf('$%s', $this->contextual[$concrete][$name]));
172
                continue;
173
            }
174
175
            // 如果没有类或接口的类型约束, 并且没有绑定上下文, 且参数没有默认值, 就直接抛异常退出掉
176 1
            if (!$parameter->isDefaultValueAvailable()) {
177 1
                throw new BindingResolutionException(
178 1
                    sprintf(
179 1
                        'parameter %s has no default value in %s',
180 1
                        $parameter->getName(),
181 1
                        $parameter->getDeclaringClass()->getName()
182
                    )
183
                );
184
            }
185 1
            $result[] = $parameter->getDefaultValue();
186
        }
187
188 4
        return $result;
189
    }
190
191
    /**
192
     * @param $implementation
193
     *
194
     * @throws ReflectionException
195
     *
196
     * @return mixed|object
197
     */
198 2
    protected function buildContextualBinding($implementation)
199
    {
200 2
        if ($implementation instanceof Closure) {
201 2
            return $implementation($this);
202
        }
203
204
        if (is_object($implementation)) {
205
            return $implementation;
206
        }
207
208
        return $this->make($implementation);
209
    }
210
211
    /**
212
     * @param $name
213
     *
214
     * @return string|Closure
215
     */
216 14
    protected function getConcrete($name)
217
    {
218 14
        $concrete = $this->bindings[$name] ?? $name;
219
220 14
        if (!is_object($concrete) && $concrete !== $name && isset($this->bindings[$concrete])) {
221
            $concrete = function () use ($concrete) {
222 1
                return $this->make($concrete);
223 1
            };
224
        }
225
226 14
        return $concrete;
227
    }
228
229
    /**
230
     * @param mixed $offset
231
     *
232
     * @return bool
233
     */
234 2
    public function offsetExists($offset)
235
    {
236 2
        return $this->isBound($offset);
237
    }
238
239
    /**
240
     * @param mixed $offset
241
     *
242
     * @throws ReflectionException
243
     *
244
     * @return mixed|object
245
     */
246 1
    public function offsetGet($offset)
247
    {
248 1
        return $this->make($offset);
249
    }
250
251
    /**
252
     * @param mixed $offset
253
     * @param mixed $value
254
     */
255 2
    public function offsetSet($offset, $value)
256
    {
257 2
        $this->instances[$offset] = $value;
258 2
    }
259
260
    /**
261
     * @param mixed $offset
262
     */
263 1
    public function offsetUnset($offset)
264
    {
265 1
        unset($this->instances[$offset]);
266 1
    }
267
268
    /**
269
     *  {@inheritdoc}
270
     */
271 1
    public function get($id)
272
    {
273 1
        if (!$this->isBound($id)) {
274 1
            throw new EntryNotFoundException();
275
        }
276
277 1
        return $this->make($id);
278
    }
279
280
    /**
281
     *  {@inheritdoc}
282
     */
283 1
    public function has($id)
284
    {
285 1
        return $this->isBound($id);
286
    }
287
}
288