Di   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 252
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 67
c 1
b 0
f 0
dl 0
loc 252
rs 9.52
wmc 36

13 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 7 2
A get() 0 7 2
A register() 0 17 6
A isRegistered() 0 3 1
A registerDependencies() 0 5 3
A resolve() 0 19 3
A checkCircularDependency() 0 5 2
A instantiate() 0 9 2
A reset() 0 5 1
A instantiable() 0 3 2
B resolveParameter() 0 23 7
A resolveParameters() 0 9 2
A autowire() 0 12 3
1
<?php
2
3
/**
4
 * Quantum PHP Framework
5
 *
6
 * An open source software development framework for PHP
7
 *
8
 * @package Quantum
9
 * @author Arman Ag. <[email protected]>
10
 * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
11
 * @link http://quantum.softberg.org/
12
 * @since 3.0.0
13
 */
14
15
namespace Quantum\Di;
16
17
use Quantum\Di\Exceptions\DiException;
18
use ReflectionException;
19
use ReflectionParameter;
20
use ReflectionFunction;
21
use ReflectionMethod;
22
use ReflectionClass;
23
use Closure;
24
25
/**
26
 * Di Class
27
 * @package Quantum/Di
28
 */
29
class Di
30
{
31
    /**
32
     * @var array
33
     */
34
    private static $dependencies = [];
35
36
    /**
37
     * @var array
38
     */
39
    private static $container = [];
40
41
    /**
42
     * @var array
43
     */
44
    private static $resolving = [];
45
46
    /**
47
     * Register dependencies
48
     */
49
    public static function registerDependencies(array $dependencies)
50
    {
51
        foreach ($dependencies as $abstract => $concrete) {
52
            if (!self::isRegistered($abstract)) {
53
                self::register($concrete, $abstract);
54
            }
55
        }
56
    }
57
58
    /**
59
     * Registers new dependency
60
     * @param string $concrete
61
     * @param string|null $abstract
62
     * @throws DiException
63
     */
64
    public static function register(string $concrete, ?string $abstract = null)
65
    {
66
        $key = $abstract ?? $concrete;
67
68
        if (isset(self::$dependencies[$key])) {
69
            throw DiException::dependencyAlreadyRegistered($key);
70
        }
71
72
        if (!class_exists($concrete)) {
73
            throw DiException::dependencyNotInstantiable($concrete);
74
        }
75
76
        if ($abstract !== null && !class_exists($abstract) && !interface_exists($abstract)) {
77
            throw DiException::invalidAbstractDependency($abstract);
78
        }
79
80
        self::$dependencies[$key] = $concrete;
81
    }
82
83
    /**
84
     * Checks if a dependency registered
85
     * @param string $abstract
86
     * @return bool
87
     */
88
    public static function isRegistered(string $abstract): bool
89
    {
90
        return isset(self::$dependencies[$abstract]);
91
    }
92
93
    /**
94
     * Retrieves a shared instance of the given dependency.
95
     * @param string $dependency
96
     * @param array $args
97
     * @return mixed
98
     * @throws DiException
99
     * @throws ReflectionException
100
     */
101
    public static function get(string $dependency, array $args = [])
102
    {
103
        if (!self::isRegistered($dependency)) {
104
            throw DiException::dependencyNotRegistered($dependency);
105
        }
106
107
        return self::resolve($dependency, $args, true);
108
    }
109
110
    /**
111
     * Creates new instance of the given dependency.
112
     * @param string $dependency
113
     * @param array $args
114
     * @return mixed
115
     * @throws DiException
116
     * @throws ReflectionException
117
     */
118
    public static function create(string $dependency, array $args = [])
119
    {
120
        if (!self::isRegistered($dependency)) {
121
            self::register($dependency);
122
        }
123
124
        return self::resolve($dependency, $args, false);
125
    }
126
127
    /**
128
     * Automatically resolves and injects parameters for a callable.
129
     * @param callable $entry
130
     * @param array $args
131
     * @return array
132
     * @throws DiException
133
     * @throws ReflectionException
134
     */
135
    public static function autowire(callable $entry, array $args = []): array
136
    {
137
        if ($entry instanceof Closure) {
138
            $reflection = new ReflectionFunction($entry);
139
        } elseif (is_array($entry)) {
140
            [$target, $method] = $entry;
141
            $reflection = new ReflectionMethod($target, $method);
142
        } else {
143
            throw DiException::invalidCallable();
144
        }
145
146
        return self::resolveParameters($reflection->getParameters(), $args);
147
    }
148
149
    /**
150
     * @return void
151
     */
152
    public static function reset(): void
153
    {
154
        self::$dependencies = [];
155
        self::$container = [];
156
        self::$resolving = [];
157
    }
158
159
    /**
160
     * Resolves the dependency
161
     * @param string $abstract
162
     * @param array $args
163
     * @param bool $singleton
164
     * @return mixed
165
     * @throws DiException
166
     * @throws ReflectionException
167
     */
168
    private static function resolve(string $abstract, array $args = [], bool $singleton = true)
169
    {
170
        self::checkCircularDependency($abstract);
171
172
        self::$resolving[$abstract] = true;
173
174
        try {
175
            $concrete = self::$dependencies[$abstract];
176
177
            if ($singleton) {
178
                if (!isset(self::$container[$abstract])) {
179
                    self::$container[$abstract] = self::instantiate($concrete, $args);
180
                }
181
                return self::$container[$abstract];
182
            }
183
184
            return self::instantiate($concrete, $args);
185
        } finally {
186
            unset(self::$resolving[$abstract]);
187
        }
188
    }
189
190
    /**
191
     * Instantiates the dependency
192
     * @param string $concrete
193
     * @param array $args
194
     * @return mixed
195
     * @throws DiException
196
     * @throws ReflectionException
197
     */
198
    private static function instantiate(string $concrete, array $args = [])
199
    {
200
        $class = new ReflectionClass($concrete);
201
202
        $constructor = $class->getConstructor();
203
204
        $params = $constructor ? self::resolveParameters($constructor->getParameters(), $args) : [];
205
206
        return new $concrete(...$params);
207
    }
208
209
    /**
210
     * Resolves all parameters
211
     * @param array $parameters
212
     * @param array $args
213
     * @return array
214
     * @throws DiException
215
     * @throws ReflectionException
216
     */
217
    private static function resolveParameters(array $parameters, array &$args = []): array
218
    {
219
        $resolved = [];
220
221
        foreach ($parameters as $param) {
222
            $resolved[] = self::resolveParameter($param, $args);
223
        }
224
225
        return $resolved;
226
    }
227
228
    /**
229
     * Resolves the parameter
230
     * @param ReflectionParameter $param
231
     * @param array $args
232
     * @return array|mixed|null
233
     * @throws DiException
234
     * @throws ReflectionException
235
     */
236
    private static function resolveParameter(ReflectionParameter $param, array &$args = [])
237
    {
238
        $type = null;
239
240
        if ($param->getType() instanceof \ReflectionNamedType) {
241
            $type = $param->getType()->getName();
242
        }
243
244
        $concrete = self::$dependencies[$type] ?? $type;
245
246
        if ($concrete && self::instantiable($concrete)) {
247
            return self::create($concrete);
248
        }
249
250
        if ($type === 'array') {
251
            return $args;
252
        }
253
254
        if ($args !== []) {
255
            return array_shift($args);
256
        }
257
258
        return $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null;
259
    }
260
261
    /**
262
     * Checks if the class is instantiable
263
     * @param string $class
264
     * @return bool
265
     */
266
    protected static function instantiable(string $class): bool
267
    {
268
        return class_exists($class) && (new ReflectionClass($class))->isInstantiable();
269
    }
270
271
    /**
272
     * @param string $abstract
273
     * @return void
274
     * @throws DiException
275
     */
276
    private static function checkCircularDependency(string $abstract): void
277
    {
278
        if (isset(self::$resolving[$abstract])) {
279
            $chain = implode(' -> ', array_keys(self::$resolving)) . ' -> ' . $abstract;
280
            throw DiException::circularDependency($chain);
281
        }
282
    }
283
}
284