Passed
Push — master ( ba91c3...f24780 )
by 光春
02:03
created

DtaContainer::make()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 10
c 1
b 0
f 0
nc 5
nop 3
dl 0
loc 19
rs 9.2222
1
<?php
2
3
namespace dtapps\library;
4
5
use Closure;
6
use Exception;
7
use InvalidArgumentException;
8
use Psr\Container\ContainerInterface;
9
use ReflectionClass;
10
use ReflectionException;
11
use ReflectionFunction;
12
use ReflectionFunctionAbstract;
13
14
/**
15
 * 容器管理类 支持PSR-11
16
 * 代码参考ThinkPHP6的Container.php
17
 * Class DtaContainer
18
 * @package dtapps\library
19
 */
20
class DtaContainer implements ContainerInterface
21
{
22
    /**
23
     * 容器对象实例
24
     * @var DtaContainer|Closure
25
     */
26
    protected static $instance;
27
28
    /**
29
     * 容器中的对象实例
30
     * @var array
31
     */
32
    protected $instances = [];
33
34
    /**
35
     * 容器绑定标识
36
     * @var array
37
     */
38
    protected $bind = [];
39
40
    /**
41
     * 容器回调
42
     * @var array
43
     */
44
    protected $invokeCallback = [];
45
46
    /**
47
     * 获取当前容器的实例(单例)
48
     * @access public
49
     * @return static
50
     */
51
    public static function getInstance()
52
    {
53
        if (is_null(static::$instance)) {
54
            static::$instance = new static;
55
        }
56
57
        if (static::$instance instanceof Closure) {
58
            return (static::$instance)();
59
        }
60
61
        return static::$instance;
62
    }
63
64
    /**
65
     * 获取容器中的对象实例
66
     * @access public
67
     * @param string $abstract 类名或者标识
68
     * @return object
69
     * @throws ReflectionException
70
     * @throws Exception
71
     */
72
    public function get($abstract)
73
    {
74
        if ($this->has($abstract)) {
75
            return $this->make($abstract);
76
        }
77
78
        throw new Exception('class not exists: ' . $abstract, $abstract);
0 ignored issues
show
Bug introduced by
$abstract of type string is incompatible with the type integer expected by parameter $code of Exception::__construct(). ( Ignorable by Annotation )

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

78
        throw new Exception('class not exists: ' . $abstract, /** @scrutinizer ignore-type */ $abstract);
Loading history...
79
    }
80
81
    /**
82
     * 根据别名获取真实类名
83
     * @param string $abstract
84
     * @return string
85
     */
86
    public function getAlias(string $abstract): string
87
    {
88
        if (isset($this->bind[$abstract])) {
89
            $bind = $this->bind[$abstract];
90
91
            if (is_string($bind)) {
92
                return $this->getAlias($bind);
93
            }
94
        }
95
96
        return $abstract;
97
    }
98
99
    /**
100
     * 判断容器中是否存在类及标识
101
     * @access public
102
     * @param string $abstract 类名或者标识
103
     * @return bool
104
     */
105
    public function bound(string $abstract): bool
106
    {
107
        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
108
    }
109
110
    /**
111
     * 判断容器中是否存在类及标识
112
     * @access public
113
     * @param string $name 类名或者标识
114
     * @return bool
115
     */
116
    public function has($name): bool
117
    {
118
        return $this->bound($name);
119
    }
120
121
    /**
122
     * 创建类的实例 已经存在则直接获取
123
     * @access public
124
     * @param string $abstract 类名或者标识
125
     * @param array $vars 变量
126
     * @param bool $newInstance 是否每次创建新的实例
127
     * @return mixed
128
     * @throws ReflectionException
129
     */
130
    public function make(string $abstract, array $vars = [], bool $newInstance = false)
131
    {
132
        $abstract = $this->getAlias($abstract);
133
134
        if (isset($this->instances[$abstract]) && !$newInstance) {
135
            return $this->instances[$abstract];
136
        }
137
138
        if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
139
            $object = $this->invokeFunction($this->bind[$abstract], $vars);
140
        } else {
141
            $object = $this->invokeClass($abstract, $vars);
142
        }
143
144
        if (!$newInstance) {
145
            $this->instances[$abstract] = $object;
146
        }
147
148
        return $object;
149
    }
150
151
    /**
152
     * 执行函数或者闭包方法 支持参数调用
153
     * @access public
154
     * @param string|Closure $function 函数或者闭包
155
     * @param array $vars 参数
156
     * @return mixed
157
     * @throws ReflectionException
158
     * @throws Exception
159
     */
160
    public function invokeFunction($function, array $vars = [])
161
    {
162
        try {
163
            $reflect = new ReflectionFunction($function);
164
        } catch (ReflectionException $e) {
165
            throw new Exception("function not exists: {$function}()", $function, $e);
0 ignored issues
show
Bug introduced by
$function of type Closure|string is incompatible with the type integer expected by parameter $code of Exception::__construct(). ( Ignorable by Annotation )

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

165
            throw new Exception("function not exists: {$function}()", /** @scrutinizer ignore-type */ $function, $e);
Loading history...
166
        }
167
168
        $args = $this->bindParams($reflect, $vars);
169
170
        return $function(...$args);
171
    }
172
173
    /**
174
     * 调用反射执行类的实例化 支持依赖注入
175
     * @access public
176
     * @param string $class 类名
177
     * @param array $vars 参数
178
     * @return mixed
179
     * @throws ReflectionException
180
     * @throws Exception
181
     */
182
    public function invokeClass(string $class, array $vars = [])
183
    {
184
        try {
185
            $reflect = new ReflectionClass($class);
186
        } catch (ReflectionException $e) {
187
            throw new Exception('class not exists: ' . $class, $class, $e);
0 ignored issues
show
Bug introduced by
$class of type string is incompatible with the type integer expected by parameter $code of Exception::__construct(). ( Ignorable by Annotation )

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

187
            throw new Exception('class not exists: ' . $class, /** @scrutinizer ignore-type */ $class, $e);
Loading history...
188
        }
189
190
        if ($reflect->hasMethod('__make')) {
191
            $method = $reflect->getMethod('__make');
192
            if ($method->isPublic() && $method->isStatic()) {
193
                $args = $this->bindParams($method, $vars);
194
                $object = $method->invokeArgs(null, $args);
195
                $this->invokeAfter($class, $object);
196
                return $object;
197
            }
198
        }
199
200
        $constructor = $reflect->getConstructor();
201
202
        $args = $constructor ? $this->bindParams($constructor, $vars) : [];
203
204
        $object = $reflect->newInstanceArgs($args);
205
206
        $this->invokeAfter($class, $object);
207
208
        return $object;
209
    }
210
211
    /**
212
     * 执行invokeClass回调
213
     * @access protected
214
     * @param string $class 对象类名
215
     * @param object $object 容器对象实例
216
     * @return void
217
     */
218
    protected function invokeAfter(string $class, $object): void
219
    {
220
        if (isset($this->invokeCallback['*'])) {
221
            foreach ($this->invokeCallback['*'] as $callback) {
222
                $callback($object, $this);
223
            }
224
        }
225
226
        if (isset($this->invokeCallback[$class])) {
227
            foreach ($this->invokeCallback[$class] as $callback) {
228
                $callback($object, $this);
229
            }
230
        }
231
    }
232
233
    /**
234
     * 绑定参数
235
     * @access protected
236
     * @param ReflectionFunctionAbstract $reflect 反射类
237
     * @param array $vars 参数
238
     * @return array
239
     * @throws ReflectionException
240
     */
241
    protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array
242
    {
243
        if ($reflect->getNumberOfParameters() == 0) {
244
            return [];
245
        }
246
247
        // 判断数组类型 数字数组时按顺序绑定参数
248
        reset($vars);
249
        $type = key($vars) === 0 ? 1 : 0;
250
        $params = $reflect->getParameters();
251
        $args = [];
252
253
        foreach ($params as $param) {
254
            $name = $param->getName();
255
            $lowerName = self::snake($name);
256
            $reflectionType = $param->getType();
257
258
            if ($reflectionType && $reflectionType->isBuiltin() === false) {
259
                $args[] = $this->getObjectParam($reflectionType->getName(), $vars);
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

259
                $args[] = $this->getObjectParam($reflectionType->/** @scrutinizer ignore-call */ getName(), $vars);
Loading history...
260
            } elseif (1 == $type && !empty($vars)) {
261
                $args[] = array_shift($vars);
262
            } elseif (0 == $type && array_key_exists($name, $vars)) {
263
                $args[] = $vars[$name];
264
            } elseif (0 == $type && array_key_exists($lowerName, $vars)) {
265
                $args[] = $vars[$lowerName];
266
            } elseif ($param->isDefaultValueAvailable()) {
267
                $args[] = $param->getDefaultValue();
268
            } else {
269
                throw new InvalidArgumentException('method param miss:' . $name);
270
            }
271
        }
272
273
        return $args;
274
    }
275
276
    protected static $snakeCache = [];
277
278
    /**
279
     * 驼峰转下划线
280
     *
281
     * @param string $value
282
     * @param string $delimiter
283
     * @return string
284
     */
285
    private static function snake(string $value, string $delimiter = '_'): string
286
    {
287
        $key = $value;
288
289
        if (isset(static::$snakeCache[$key][$delimiter])) {
290
            return static::$snakeCache[$key][$delimiter];
291
        }
292
293
        if (!ctype_lower($value)) {
294
            $value = preg_replace('/\s+/u', '', $value);
295
296
            $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value));
297
        }
298
299
        return static::$snakeCache[$key][$delimiter] = $value;
300
    }
301
302
    /**
303
     * 字符串转小写
304
     *
305
     * @param string $value
306
     * @return string
307
     */
308
    private static function lower(string $value): string
309
    {
310
        return mb_strtolower($value, 'UTF-8');
311
    }
312
313
    /**
314
     * 获取对象类型的参数值
315
     * @access protected
316
     * @param string $className 类名
317
     * @param array $vars 参数
318
     * @return mixed
319
     * @throws ReflectionException
320
     */
321
    protected function getObjectParam(string $className, array &$vars)
322
    {
323
        $array = $vars;
324
        $value = array_shift($array);
325
326
        if ($value instanceof $className) {
327
            $result = $value;
328
            array_shift($vars);
329
        } else {
330
            $result = $this->make($className);
331
        }
332
333
        return $result;
334
    }
335
336
    public function __get($name)
337
    {
338
        return $this->get($name);
339
    }
340
341
    public function offsetGet($key)
342
    {
343
        return $this->make($key);
344
    }
345
}
346