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

Di::userDependencies()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 9
rs 10
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 ReflectionClass;
23
24
/**
25
 * Di Class
26
 * @package Quantum/Di
27
 */
28
class Di
29
{
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
        $reflection = is_closure($entry)
138
            ? new ReflectionFunction($entry)
139
            : 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

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

233
        $type = $param->getType() ? $param->getType()->/** @scrutinizer ignore-call */ getName() : null;
Loading history...
234
235
        $concrete = self::$dependencies[$type] ?? $type;
236
237
        if ($concrete && self::instantiable($concrete)) {
238
            return self::create($concrete);
239
        }
240
241
        if ($type === 'array') {
242
            return $args;
243
        }
244
245
        if (!empty($args)) {
246
            return array_shift($args);
247
        }
248
249
        return $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null;
250
    }
251
252
    /**
253
     * Checks if the class is instantiable
254
     * @param string $class
255
     * @return bool
256
     */
257
    protected static function instantiable(string $class): bool
258
    {
259
        return class_exists($class) && (new ReflectionClass($class))->isInstantiable();
260
    }
261
262
    /**
263
     * @param string $abstract
264
     * @return void
265
     * @throws DiException
266
     */
267
    private static function checkCircularDependency(string $abstract): void
268
    {
269
        if (isset(self::$resolving[$abstract])) {
270
            $chain = implode(' -> ', array_keys(self::$resolving)) . ' -> ' . $abstract;
271
            throw DiException::circularDependency($chain);
272
        }
273
    }
274
}
275