Completed
Pull Request — 6.0 (#2115)
by nhzex
06:13
created

Container::bindParams()   C

Complexity

Conditions 12
Paths 15

Size

Total Lines 33
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 12.0117

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 23
c 1
b 0
f 0
nc 15
nop 2
dl 0
loc 33
ccs 22
cts 23
cp 0.9565
crap 12.0117
rs 6.9666

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
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: liu21st <[email protected]>
10
// +----------------------------------------------------------------------
11
declare (strict_types = 1);
12
13
namespace think;
14
15
use ArrayAccess;
16
use ArrayIterator;
17
use Closure;
18
use Countable;
19
use InvalidArgumentException;
20
use IteratorAggregate;
21
use Psr\Container\ContainerInterface;
22
use ReflectionClass;
23
use ReflectionException;
24
use ReflectionFunction;
25
use ReflectionFunctionAbstract;
26
use ReflectionMethod;
27
use think\exception\ClassNotFoundException;
28
use think\exception\FuncNotFoundException;
29
use think\helper\Str;
30
31
/**
32
 * 容器管理类 支持PSR-11
33
 */
34
class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable
35
{
36
    /**
37
     * 容器对象实例
38
     * @var Container|Closure
39
     */
40
    protected static $instance;
41
42
    /**
43
     * 容器中的对象实例
44
     * @var array
45
     */
46
    protected $instances = [];
47
48
    /**
49
     * 容器绑定标识
50
     * @var array
51
     */
52
    protected $bind = [];
53
54
    /**
55
     * 容器回调
56
     * @var array
57
     */
58
    protected $invokeCallback = [];
59
60
    /**
61
     * 获取当前容器的实例(单例)
62
     * @access public
63
     * @return static
64
     */
65 3
    public static function getInstance()
66
    {
67 3
        if (is_null(static::$instance)) {
68 1
            static::$instance = new static;
69
        }
70
71 3
        if (static::$instance instanceof Closure) {
72 1
            return (static::$instance)();
73
        }
74
75 3
        return static::$instance;
76
    }
77
78
    /**
79
     * 设置当前容器的实例
80
     * @access public
81
     * @param object|Closure $instance
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
82
     * @return void
83
     */
84 53
    public static function setInstance($instance): void
85
    {
86 53
        static::$instance = $instance;
87 53
    }
88
89
    /**
90
     * 注册一个容器对象回调
91
     *
92
     * @param string|Closure $abstract
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
93
     * @param Closure|null   $callback
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
94
     * @return void
95
     */
96 1
    public function resolving($abstract, Closure $callback = null): void
97
    {
98 1
        if ($abstract instanceof Closure) {
99 1
            $this->invokeCallback['*'][] = $abstract;
100 1
            return;
101
        }
102
103 1
        $abstract = $this->getAlias($abstract);
104
105 1
        $this->invokeCallback[$abstract][] = $callback;
106 1
    }
107
108
    /**
109
     * 获取容器中的对象实例 不存在则创建
110
     * @access public
111
     * @param string     $abstract    类名或者标识
112
     * @param array|true $vars        变量
113
     * @param bool       $newInstance 是否每次创建新的实例
114
     * @return object
115
     */
116 1
    public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
117
    {
118 1
        return static::getInstance()->make($abstract, $vars, $newInstance);
119
    }
120
121
    /**
122
     * 获取容器中的对象实例
123
     * @access public
124
     * @param string $abstract 类名或者标识
125
     * @return object
126
     */
127 9
    public function get($abstract)
128
    {
129 9
        if ($this->has($abstract)) {
130 8
            return $this->make($abstract);
131
        }
132
133 1
        throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
134
    }
135
136
    /**
137
     * 绑定一个类、闭包、实例、接口实现到容器
138
     * @access public
139
     * @param string|array $abstract 类标识、接口
140
     * @param mixed        $concrete 要绑定的类、闭包或者实例
141
     * @return $this
142
     */
143 9
    public function bind($abstract, $concrete = null)
144
    {
145 9
        if (is_array($abstract)) {
146 3
            foreach ($abstract as $key => $val) {
147 2
                $this->bind($key, $val);
148
            }
149 8
        } elseif ($concrete instanceof Closure) {
150 3
            $this->bind[$abstract] = $concrete;
151 6
        } elseif (is_object($concrete)) {
152 4
            $this->instance($abstract, $concrete);
153
        } else {
154 3
            $abstract = $this->getAlias($abstract);
155
156 3
            $this->bind[$abstract] = $concrete;
157
        }
158
159 9
        return $this;
160
    }
161
162
    /**
163
     * 根据别名获取真实类名
164
     * @param  string $abstract
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
165
     * @return string
166
     */
167 28
    public function getAlias(string $abstract): string
168
    {
169 28
        if (isset($this->bind[$abstract])) {
170 17
            $bind = $this->bind[$abstract];
171
172 17
            if (is_string($bind)) {
173 15
                return $this->getAlias($bind);
174
            }
175
        }
176
177 28
        return $abstract;
178
    }
179
180
    /**
181
     * 绑定一个类实例到容器
182
     * @access public
183
     * @param string $abstract 类名或者标识
184
     * @param object $instance 类的实例
185
     * @return $this
186
     */
187 15
    public function instance(string $abstract, $instance)
188
    {
189 15
        $abstract = $this->getAlias($abstract);
190
191 15
        $this->instances[$abstract] = $instance;
192
193 15
        return $this;
194
    }
195
196
    /**
197
     * 判断容器中是否存在类及标识
198
     * @access public
199
     * @param string $abstract 类名或者标识
200
     * @return bool
201
     */
202 9
    public function bound(string $abstract): bool
203
    {
204 9
        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
205
    }
206
207
    /**
208
     * 判断容器中是否存在类及标识
209
     * @access public
210
     * @param string $name 类名或者标识
211
     * @return bool
212
     */
213 9
    public function has($name): bool
214
    {
215 9
        return $this->bound($name);
216
    }
217
218
    /**
219
     * 判断容器中是否存在对象实例
220
     * @access public
221
     * @param string $abstract 类名或者标识
222
     * @return bool
223
     */
224 4
    public function exists(string $abstract): bool
225
    {
226 4
        $abstract = $this->getAlias($abstract);
227
228 4
        return isset($this->instances[$abstract]);
229
    }
230
231
    /**
232
     * 创建类的实例 已经存在则直接获取
233
     * @access public
234
     * @param string $abstract    类名或者标识
235
     * @param array  $vars        变量
236
     * @param bool   $newInstance 是否每次创建新的实例
237
     * @return mixed
238
     */
239 20
    public function make(string $abstract, array $vars = [], bool $newInstance = false)
240
    {
241 20
        $abstract = $this->getAlias($abstract);
242
243 20
        if (isset($this->instances[$abstract]) && !$newInstance) {
244 9
            return $this->instances[$abstract];
245
        }
246
247 18
        if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
248 3
            $object = $this->invokeFunction($this->bind[$abstract], $vars);
249
        } else {
250 15
            $object = $this->invokeClass($abstract, $vars);
251
        }
252
253 18
        if (!$newInstance) {
254 18
            $this->instances[$abstract] = $object;
255
        }
256
257 18
        return $object;
258
    }
259
260
    /**
261
     * 删除容器中的对象实例
262
     * @access public
263
     * @param string $name 类名或者标识
264
     * @return void
265
     */
266 2
    public function delete($name)
267
    {
268 2
        $name = $this->getAlias($name);
269
270 2
        if (isset($this->instances[$name])) {
271 2
            unset($this->instances[$name]);
272
        }
273 2
    }
274
275
    /**
276
     * 执行函数或者闭包方法 支持参数调用
277
     * @access public
278
     * @param string|Closure $function 函数或者闭包
279
     * @param array          $vars     参数
280
     * @return mixed
281
     */
282 7
    public function invokeFunction($function, array $vars = [])
283
    {
284
        try {
285 7
            $reflect = new ReflectionFunction($function);
286 1
        } catch (ReflectionException $e) {
287 1
            throw new FuncNotFoundException("function not exists: {$function}()", $function, $e);
0 ignored issues
show
Bug introduced by
It seems like $function can also be of type Closure; however, parameter $func of think\exception\FuncNotF...xception::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

287
            throw new FuncNotFoundException("function not exists: {$function}()", /** @scrutinizer ignore-type */ $function, $e);
Loading history...
288
        }
289
290 6
        $args = $this->bindParams($reflect, $vars);
291
292 6
        return $function(...$args);
293
    }
294
295
    /**
296
     * 调用反射执行类的方法 支持参数绑定
297
     * @access public
298
     * @param mixed $method     方法
299
     * @param array $vars       参数
300
     * @param bool  $accessible 设置是否可访问
301
     * @return mixed
302
     */
303 6
    public function invokeMethod($method, array $vars = [], bool $accessible = false)
304
    {
305 6
        if (is_array($method)) {
306 6
            [$class, $method] = $method;
307
308 6
            $class = is_object($class) ? $class : $this->invokeClass($class);
309
        } else {
310
            // 静态方法
311 1
            [$class, $method] = explode('::', $method);
312
        }
313
314
        try {
315 6
            $reflect = new ReflectionMethod($class, $method);
316 1
        } catch (ReflectionException $e) {
317 1
            $class = is_object($class) ? get_class($class) : $class;
318 1
            throw new FuncNotFoundException('method not exists: ' . $class . '::' . $method . '()', "{$class}::{$method}", $e);
319
        }
320
321 5
        $args = $this->bindParams($reflect, $vars);
322
323 5
        if ($accessible) {
324 1
            $reflect->setAccessible($accessible);
325
        }
326
327 5
        return $reflect->invokeArgs(is_object($class) ? $class : null, $args);
328
    }
329
330
    /**
331
     * 调用反射执行类的方法 支持参数绑定
332
     * @access public
333
     * @param object $instance 对象实例
334
     * @param mixed  $reflect  反射类
335
     * @param array  $vars     参数
336
     * @return mixed
337
     */
338 1
    public function invokeReflectMethod($instance, $reflect, array $vars = [])
339
    {
340 1
        $args = $this->bindParams($reflect, $vars);
341
342 1
        return $reflect->invokeArgs($instance, $args);
343
    }
344
345
    /**
346
     * 调用反射执行callable 支持参数绑定
347
     * @access public
348
     * @param mixed $callable
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
349
     * @param array $vars       参数
350
     * @param bool  $accessible 设置是否可访问
351
     * @return mixed
352
     */
353 6
    public function invoke($callable, array $vars = [], bool $accessible = false)
354
    {
355 6
        if ($callable instanceof Closure) {
356 3
            return $this->invokeFunction($callable, $vars);
357 4
        } elseif (is_string($callable) && false === strpos($callable, '::')) {
358 1
            return $this->invokeFunction($callable, $vars);
359
        } else {
360 4
            return $this->invokeMethod($callable, $vars, $accessible);
361
        }
362
    }
363
364
    /**
365
     * 调用反射执行类的实例化 支持依赖注入
366
     * @access public
367
     * @param string $class 类名
368
     * @param array  $vars  参数
369
     * @return mixed
370
     */
371 29
    public function invokeClass(string $class, array $vars = [])
372
    {
373
        try {
374 29
            $reflect = new ReflectionClass($class);
375 2
        } catch (ReflectionException $e) {
376 2
            throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
377
        }
378
379 28
        if ($reflect->hasMethod('__make')) {
380 5
            $method = $reflect->getMethod('__make');
381 5
            if ($method->isPublic() && $method->isStatic()) {
382 5
                $args = $this->bindParams($method, $vars);
383 5
                return $method->invokeArgs(null, $args);
384
            }
385
        }
386
387 24
        $constructor = $reflect->getConstructor();
388
389 24
        $args = $constructor ? $this->bindParams($constructor, $vars) : [];
0 ignored issues
show
introduced by
$constructor is of type ReflectionMethod, thus it always evaluated to true.
Loading history...
390
391 24
        $object = $reflect->newInstanceArgs($args);
392
393 24
        $this->invokeAfter($class, $object);
394
395 24
        return $object;
396
    }
397
398
    /**
399
     * 执行invokeClass回调
400
     * @access protected
401
     * @param string $class  对象类名
402
     * @param object $object 容器对象实例
403
     * @return void
404
     */
405 24
    protected function invokeAfter(string $class, $object): void
406
    {
407 24
        if (isset($this->invokeCallback['*'])) {
408 1
            foreach ($this->invokeCallback['*'] as $callback) {
409 1
                $callback($object, $this);
410
            }
411
        }
412
413 24
        if (isset($this->invokeCallback[$class])) {
414 1
            foreach ($this->invokeCallback[$class] as $callback) {
415 1
                $callback($object, $this);
416
            }
417
        }
418 24
    }
419
420
    /**
421
     * 绑定参数
422
     * @access protected
423
     * @param ReflectionFunctionAbstract $reflect 反射类
424
     * @param array                      $vars    参数
425
     * @return array
426
     */
427 32
    protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array
428
    {
429 32
        if ($reflect->getNumberOfParameters() == 0) {
430 16
            return [];
431
        }
432
433
        // 判断数组类型 数字数组时按顺序绑定参数
434 16
        reset($vars);
435 16
        $type   = key($vars) === 0 ? 1 : 0;
436 16
        $params = $reflect->getParameters();
437 16
        $args   = [];
438
439 16
        foreach ($params as $param) {
440 16
            $name      = $param->getName();
441 16
            $lowerName = Str::snake($name);
442 16
            $class     = $param->getClass();
443
444 16
            if ($class) {
445 14
                $args[] = $this->getObjectParam($class->getName(), $vars);
446 13
            } elseif (1 == $type && !empty($vars)) {
447 11
                $args[] = array_shift($vars);
448 3
            } elseif (0 == $type && isset($vars[$name])) {
449 1
                $args[] = $vars[$name];
450 3
            } elseif (0 == $type && isset($vars[$lowerName])) {
451 1
                $args[] = $vars[$lowerName];
452 3
            } elseif ($param->isDefaultValueAvailable()) {
453 3
                $args[] = $param->getDefaultValue();
454
            } else {
455
                throw new InvalidArgumentException('method param miss:' . $name);
456
            }
457
        }
458
459 16
        return $args;
460
    }
461
462
    /**
0 ignored issues
show
Coding Style introduced by
Parameter ...$args should have a doc-comment as per coding-style.
Loading history...
463
     * 创建工厂对象实例
464
     * @param string $name      工厂类名
465
     * @param string $namespace 默认命名空间
466
     * @param array  $args
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $args does not match actual variable name ...$args
Loading history...
467
     * @return mixed
468
     * @deprecated
469
     * @access public
470
     */
471 1
    public static function factory(string $name, string $namespace = '', ...$args)
472
    {
473 1
        $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name);
474
475 1
        return Container::getInstance()->invokeClass($class, $args);
476
    }
477
478
    /**
479
     * 获取对象类型的参数值
480
     * @access protected
481
     * @param string $className 类名
482
     * @param array  $vars      参数
483
     * @return mixed
484
     */
485 14
    protected function getObjectParam(string $className, array &$vars)
486
    {
487 14
        $array = $vars;
488 14
        $value = array_shift($array);
489
490 14
        if ($value instanceof $className) {
491 1
            $result = $value;
492 1
            array_shift($vars);
493
        } else {
494 14
            $result = $this->make($className);
495
        }
496
497 14
        return $result;
498
    }
499
500 1
    public function __set($name, $value)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __set()
Loading history...
501
    {
502 1
        $this->bind($name, $value);
503 1
    }
504
505 23
    public function __get($name)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __get()
Loading history...
506
    {
507 23
        return $this->get($name);
508
    }
509
510 1
    public function __isset($name): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __isset()
Loading history...
511
    {
512 1
        return $this->exists($name);
513
    }
514
515 2
    public function __unset($name)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __unset()
Loading history...
516
    {
517 2
        $this->delete($name);
518 2
    }
519
520 1
    public function offsetExists($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetExists()
Loading history...
521
    {
522 1
        return $this->exists($key);
523
    }
524
525 1
    public function offsetGet($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetGet()
Loading history...
526
    {
527 1
        return $this->make($key);
528
    }
529
530 1
    public function offsetSet($key, $value)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetSet()
Loading history...
531
    {
532 1
        $this->bind($key, $value);
533 1
    }
534
535 1
    public function offsetUnset($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetUnset()
Loading history...
536
    {
537 1
        $this->delete($key);
538 1
    }
539
540
    //Countable
541
    public function count()
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
542
    {
543
        return count($this->instances);
544
    }
545
546
    //IteratorAggregate
547 1
    public function getIterator()
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
548
    {
549 1
        return new ArrayIterator($this->instances);
550
    }
551
}
552