Passed
Push — master ( ec5bca...4575ee )
by Runner
03:41
created

Container::build()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5

Importance

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