Passed
Push — master ( 0fc4a2...12a1e1 )
by 光春
02:06
created

Container::bindParams()   C

Complexity

Conditions 13
Paths 15

Size

Total Lines 33
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 23
nc 15
nop 2
dl 0
loc 33
rs 6.6166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare (strict_types=1);
4
5
use Psr\Container\ContainerInterface;
6
7
/**
8
 * 容器管理类 支持PSR-11
9
 * 代码参考ThinkPHP6的Container.php
10
 * Class Container
11
 */
12
class Container implements ContainerInterface
13
{
14
    /**
15
     * 容器对象实例
16
     * @var Container|Closure
17
     */
18
    protected static $instance;
19
20
    /**
21
     * 容器中的对象实例
22
     * @var array
23
     */
24
    protected $instances = [];
25
26
    /**
27
     * 容器绑定标识
28
     * @var array
29
     */
30
    protected $bind = [];
31
32
    /**
33
     * 容器回调
34
     * @var array
35
     */
36
    protected $invokeCallback = [];
37
38
    /**
39
     * 获取当前容器的实例(单例)
40
     * @access public
41
     * @return static
42
     */
43
    public static function getInstance()
44
    {
45
        if (is_null(static::$instance)) {
46
            static::$instance = new static;
47
        }
48
49
        if (static::$instance instanceof Closure) {
50
            return (static::$instance)();
51
        }
52
53
        return static::$instance;
54
    }
55
56
    /**
57
     * 获取容器中的对象实例
58
     * @access public
59
     * @param string $abstract 类名或者标识
60
     * @return object
61
     * @throws ReflectionException
62
     * @throws Exception
63
     */
64
    public function get($abstract)
65
    {
66
        if ($this->has($abstract)) {
67
            return $this->make($abstract);
68
        }
69
70
        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

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

157
            throw new Exception("function not exists: {$function}()", /** @scrutinizer ignore-type */ $function, $e);
Loading history...
158
        }
159
160
        $args = $this->bindParams($reflect, $vars);
161
162
        return $function(...$args);
163
    }
164
165
    /**
166
     * 调用反射执行类的实例化 支持依赖注入
167
     * @access public
168
     * @param string $class 类名
169
     * @param array $vars 参数
170
     * @return mixed
171
     * @throws ReflectionException
172
     * @throws Exception
173
     */
174
    public function invokeClass(string $class, array $vars = [])
175
    {
176
        try {
177
            $reflect = new ReflectionClass($class);
178
        } catch (ReflectionException $e) {
179
            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

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

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