Completed
Push — master ( aa03a2...f7bb46 )
by Runner
02:15
created

Container::bind()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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