Passed
Pull Request — master (#320)
by Arman
03:03
created

Di   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 272
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 69
c 1
b 0
f 0
dl 0
loc 272
rs 9.36
wmc 38

15 Methods

Rating   Name   Duplication   Size   Complexity  
A resolve() 0 19 3
A create() 0 7 2
A checkCircularDependency() 0 5 2
A coreDependencies() 0 6 1
A instantiate() 0 9 2
A get() 0 7 2
A reset() 0 5 1
A register() 0 17 6
A instantiable() 0 3 2
A isRegistered() 0 3 1
A userDependencies() 0 9 2
A registerDependencies() 0 5 3
A resolveParameters() 0 9 2
A autowire() 0 7 2
B resolveParameter() 0 19 7
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 2.9.8
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 Quantum\App\App;
23
use ReflectionClass;
24
25
/**
26
 * Di Class
27
 * @package Quantum/Di
28
 */
29
class Di
30
{
31
32
    /**
33
     * @var array
34
     */
35
    private static $dependencies = [];
36
37
    /**
38
     * @var array
39
     */
40
    private static $container = [];
41
42
    /**
43
     * @var array
44
     */
45
    private static $resolving = [];
46
47
    /**
48
     * Loads dependency definitions
49
     */
50
    public static function registerDependencies()
51
    {
52
        foreach (self::coreDependencies() + self::userDependencies() as $abstract => $concrete) {
53
            if (!self::isRegistered($abstract)) {
54
                self::register($concrete, $abstract);
55
            }
56
        }
57
    }
58
59
    /**
60
     * Registers new dependency
61
     * @param string $concrete
62
     * @param string|null $abstract
63
     * @throws DiException
64
     */
65
    public static function register(string $concrete, ?string $abstract = null)
66
    {
67
        $key = $abstract ?? $concrete;
68
69
        if (isset(self::$dependencies[$key])) {
70
            throw DiException::dependencyAlreadyRegistered($key);
71
        }
72
73
        if (!class_exists($concrete)) {
74
            throw DiException::dependencyNotInstantiable($concrete);
75
        }
76
77
        if ($abstract !== null && !class_exists($abstract) && !interface_exists($abstract)) {
78
            throw DiException::invalidAbstractDependency($abstract);
79
        }
80
81
        self::$dependencies[$key] = $concrete;
82
    }
83
84
    /**
85
     * Checks if a dependency registered
86
     * @param string $abstract
87
     * @return bool
88
     */
89
    public static function isRegistered(string $abstract): bool
90
    {
91
        return isset(self::$dependencies[$abstract]);
92
    }
93
94
    /**
95
     * Retrieves a shared instance of the given dependency.
96
     * @param string $dependency
97
     * @param array $args
98
     * @return mixed
99
     * @throws DiException
100
     * @throws ReflectionException
101
     */
102
    public static function get(string $dependency, array $args = [])
103
    {
104
        if (!self::isRegistered($dependency)) {
105
            throw DiException::dependencyNotRegistered($dependency);
106
        }
107
108
        return self::resolve($dependency, $args, true);
109
    }
110
111
    /**
112
     * Creates new instance of the given dependency.
113
     * @param string $dependency
114
     * @param array $args
115
     * @return mixed
116
     * @throws DiException
117
     * @throws ReflectionException
118
     */
119
    public static function create(string $dependency, array $args = [])
120
    {
121
        if (!self::isRegistered($dependency)) {
122
            self::register($dependency);
123
        }
124
125
        return self::resolve($dependency, $args, false);
126
    }
127
128
    /**
129
     * Automatically resolves and injects parameters for a callable.
130
     * @param callable $entry
131
     * @param array $args
132
     * @return array
133
     * @throws DiException
134
     * @throws ReflectionException
135
     */
136
    public static function autowire(callable $entry, array $args = []): array
137
    {
138
        $reflection = is_closure($entry)
139
            ? new ReflectionFunction($entry)
140
            : new ReflectionMethod(...$entry);
0 ignored issues
show
Bug introduced by
$entry is expanded, but the parameter $objectOrMethod of ReflectionMethod::__construct() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

140
            : new ReflectionMethod(/** @scrutinizer ignore-type */ ...$entry);
Loading history...
141
142
        return self::resolveParameters($reflection->getParameters(), $args);
143
    }
144
145
    /**
146
     * @return void
147
     */
148
    public static function reset(): void
149
    {
150
        self::$dependencies = [];
151
        self::$container = [];
152
        self::$resolving = [];
153
    }
154
155
    /**
156
     * Resolves the dependency
157
     * @param string $abstract
158
     * @param array $args
159
     * @param bool $singleton
160
     * @return mixed
161
     * @throws DiException
162
     * @throws ReflectionException
163
     */
164
    private static function resolve(string $abstract, array $args = [], bool $singleton = true)
165
    {
166
        self::checkCircularDependency($abstract);
167
168
        self::$resolving[$abstract] = true;
169
170
        try {
171
            $concrete = self::$dependencies[$abstract];
172
173
            if ($singleton) {
174
                if (!isset(self::$container[$abstract])) {
175
                    self::$container[$abstract] = self::instantiate($concrete, $args);
176
                }
177
                return self::$container[$abstract];
178
            }
179
180
            return self::instantiate($concrete, $args);
181
        } finally {
182
            unset(self::$resolving[$abstract]);
183
        }
184
    }
185
186
    /**
187
     * Instantiates the dependency
188
     * @param string $concrete
189
     * @param array $args
190
     * @return mixed
191
     * @throws DiException
192
     * @throws ReflectionException
193
     */
194
    private static function instantiate(string $concrete, array $args = [])
195
    {
196
        $class = new ReflectionClass($concrete);
197
198
        $constructor = $class->getConstructor();
199
200
        $params = $constructor ? self::resolveParameters($constructor->getParameters(), $args) : [];
201
202
        return new $concrete(...$params);
203
    }
204
205
    /**
206
     * Resolves all parameters
207
     * @param array $parameters
208
     * @param array $args
209
     * @return array
210
     * @throws DiException
211
     * @throws ReflectionException
212
     */
213
    private static function resolveParameters(array $parameters, array &$args = []): array
214
    {
215
        $resolved = [];
216
217
        foreach ($parameters as $param) {
218
            $resolved[] = self::resolveParameter($param, $args);
219
        }
220
221
        return $resolved;
222
    }
223
224
    /**
225
     * Resolves the parameter
226
     * @param ReflectionParameter $param
227
     * @param array|null $args
228
     * @return array|mixed|null
229
     * @throws DiException
230
     * @throws ReflectionException
231
     */
232
    private static function resolveParameter(ReflectionParameter $param, ?array &$args = [])
233
    {
234
        $type = $param->getType() ? $param->getType()->getName() : null;
0 ignored issues
show
Bug introduced by
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

234
        $type = $param->getType() ? $param->getType()->/** @scrutinizer ignore-call */ getName() : null;
Loading history...
235
236
        $concrete = self::$dependencies[$type] ?? $type;
237
238
        if ($concrete && self::instantiable($concrete)) {
239
            return self::create($concrete);
240
        }
241
242
        if ($type === 'array') {
243
            return $args;
244
        }
245
246
        if (!empty($args)) {
247
            return array_shift($args);
248
        }
249
250
        return $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null;
251
    }
252
253
    /**
254
     * Checks if the class is instantiable
255
     * @param string $class
256
     * @return bool
257
     */
258
    protected static function instantiable(string $class): bool
259
    {
260
        return class_exists($class) && (new ReflectionClass($class))->isInstantiable();
261
    }
262
263
    /**
264
     * @param string $abstract
265
     * @return void
266
     * @throws DiException
267
     */
268
    private static function checkCircularDependency(string $abstract): void
269
    {
270
        if (isset(self::$resolving[$abstract])) {
271
            $chain = implode(' -> ', array_keys(self::$resolving)) . ' -> ' . $abstract;
272
            throw DiException::circularDependency($chain);
273
        }
274
    }
275
276
    /**
277
     * Loads user defined dependencies
278
     * @return array
279
     */
280
    private static function userDependencies(): array
281
    {
282
        $userDependencies = App::getBaseDir() . DS . 'shared' . DS . 'config' . DS . 'dependencies.php';
283
284
        if (!file_exists($userDependencies)) {
285
            return [];
286
        }
287
288
        return (array)require_once $userDependencies;
289
    }
290
291
    /**
292
     * Loads the core dependencies
293
     * @return array
294
     */
295
    private static function coreDependencies(): array
296
    {
297
        return [
298
            \Quantum\Loader\Loader::class => \Quantum\Loader\Loader::class,
299
            \Quantum\Http\Request::class => \Quantum\Http\Request::class,
300
            \Quantum\Http\Response::class => \Quantum\Http\Response::class,
301
        ];
302
    }
303
}
304