Test Setup Failed
Push — master ( 9fc29e...a0a9ea )
by Runner
05:30 queued 23s
created

Container::buildContextualBinding()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 6

Importance

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