Completed
Push — master ( 2ff04e...b898e3 )
by Runner
02:30
created

Container::make()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

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