Completed
Pull Request — 6.0 (#1895)
by nhzex
04:53
created

Container   F

Complexity

Total Complexity 83

Size/Duplication

Total Lines 534
Duplicated Lines 0 %

Test Coverage

Coverage 90.45%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 177
c 1
b 0
f 0
dl 0
loc 534
ccs 161
cts 178
cp 0.9045
rs 2
wmc 83

30 Methods

Rating   Name   Duplication   Size   Complexity  
A setInstance() 0 3 1
A pull() 0 3 1
A bound() 0 3 2
A getInstance() 0 11 3
A resolving() 0 12 3
A get() 0 7 2
A has() 0 3 1
A make() 0 23 6
A bind() 0 13 4
A instance() 0 13 3
A exists() 0 11 3
A delete() 0 13 4
A __set() 0 3 1
C bindParams() 0 33 12
A invokeFunction() 0 21 6
A invokeReflectMethod() 0 5 1
A invokeClass() 0 25 6
A __isset() 0 3 1
A invokeAfter() 0 11 5
A getObjectParam() 0 13 2
A __unset() 0 3 1
A invokeMethod() 0 23 6
A offsetExists() 0 3 1
A offsetGet() 0 3 1
A getIterator() 0 3 1
A offsetSet() 0 3 1
A invoke() 0 7 2
A __get() 0 3 1
A offsetUnset() 0 3 1
A count() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Container often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Container, and based on these observations, apply Extract Interface, too.

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 ReflectionMethod;
26
use think\exception\ClassNotFoundException;
27
28
/**
29
 * Class Container
30
 * @package think
0 ignored issues
show
Coding Style introduced by
Package name "think" is not valid; consider "Think" instead
Loading history...
31
 *
32
 * @property Route      $route
33
 * @property Config     $config
34
 * @property Cache      $cache
35
 * @property Request    $request
36
 * @property Http       $http
37
 * @property Console    $console
38
 * @property Env        $env
39
 * @property Event      $event
40
 * @property Middleware $middleware
41
 * @property Log        $log
42
 * @property Lang       $lang
43
 * @property Db         $db
44
 * @property Cookie     $cookie
45
 * @property Session    $session
46
 * @property Validate   $validate
47
 * @property Filesystem $filesystem
48
 */
49
class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable
50
{
51
    /**
52
     * 容器对象实例
53
     * @var Container|Closure
54
     */
55
    protected static $instance;
56
57
    /**
58
     * 容器中的对象实例
59
     * @var array
60
     */
61
    protected $instances = [];
62
63
    /**
64
     * 容器绑定标识
65
     * @var array
66
     */
67
    protected $bind = [
68
        'app'                     => App::class,
69
        'cache'                   => Cache::class,
70
        'config'                  => Config::class,
71
        'console'                 => Console::class,
72
        'cookie'                  => Cookie::class,
73
        'db'                      => Db::class,
74
        'env'                     => Env::class,
75
        'event'                   => Event::class,
76
        'http'                    => Http::class,
77
        'lang'                    => Lang::class,
78
        'log'                     => Log::class,
79
        'middleware'              => Middleware::class,
80
        'request'                 => Request::class,
81
        'response'                => Response::class,
82
        'route'                   => Route::class,
83
        'session'                 => Session::class,
84
        'validate'                => Validate::class,
85
        'view'                    => View::class,
86
        'filesystem'              => Filesystem::class,
87
88
        // 接口依赖注入
89
        'Psr\Log\LoggerInterface' => Log::class,
90
    ];
91
92
    /**
93
     * 容器回调
94
     *
95
     * @var array
96
     */
97
    protected $invokeCallback = [];
98
99
    /**
100
     * 获取当前容器的实例(单例)
101
     * @access public
102
     * @return static
103
     */
104 6
    public static function getInstance()
105
    {
106 6
        if (is_null(static::$instance)) {
107 1
            static::$instance = new static;
108
        }
109
110 6
        if (static::$instance instanceof Closure) {
111 1
            return (static::$instance)();
112
        }
113
114 6
        return static::$instance;
115
    }
116
117
    /**
118
     * 设置当前容器的实例
119
     * @access public
120
     * @param object|Closure $instance
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
121
     * @return void
122
     */
123 33
    public static function setInstance($instance): void
124
    {
125 33
        static::$instance = $instance;
126 33
    }
127
128
    /**
129
     * 注册一个容器对象回调
130
     *
131
     * @param  string|Closure $abstract
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
132
     * @param  Closure|null   $callback
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
133
     * @return void
134
     */
135
    public function resolving($abstract, Closure $callback = null): void
136
    {
137
        if ($abstract instanceof Closure) {
138
            $this->invokeCallback['*'][] = $abstract;
139
            return;
140
        }
141
142
        if (isset($this->bind[$abstract])) {
143
            $abstract = $this->bind[$abstract];
144
        }
145
146
        $this->invokeCallback[$abstract][] = $callback;
147
    }
148
149
    /**
150
     * 获取容器中的对象实例 不存在则创建
151
     * @access public
152
     * @param string     $abstract    类名或者标识
153
     * @param array|true $vars        变量
154
     * @param bool       $newInstance 是否每次创建新的实例
155
     * @return object
156
     */
157 1
    public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
158
    {
159 1
        return static::getInstance()->make($abstract, $vars, $newInstance);
160
    }
161
162
    /**
163
     * 获取容器中的对象实例
164
     * @access public
165
     * @param string $abstract 类名或者标识
166
     * @return object
167
     */
168 12
    public function get($abstract)
169
    {
170 12
        if ($this->has($abstract)) {
171 11
            return $this->make($abstract);
172
        }
173
174 1
        throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
175
    }
176
177
    /**
178
     * 绑定一个类、闭包、实例、接口实现到容器
179
     * @access public
180
     * @param string|array $abstract 类标识、接口
181
     * @param mixed        $concrete 要绑定的类、闭包或者实例
182
     * @return $this
183
     */
184 9
    public function bind($abstract, $concrete = null)
185
    {
186 9
        if (is_array($abstract)) {
187 4
            $this->bind = array_merge($this->bind, $abstract);
188 6
        } elseif ($concrete instanceof Closure) {
189 3
            $this->bind[$abstract] = $concrete;
190 4
        } elseif (is_object($concrete)) {
191 3
            $this->instance($abstract, $concrete);
192
        } else {
193 2
            $this->bind[$abstract] = $concrete;
194
        }
195
196 9
        return $this;
197
    }
198
199
    /**
200
     * 绑定一个类实例到容器
201
     * @access public
202
     * @param string $abstract 类名或者标识
203
     * @param object $instance 类的实例
204
     * @return $this
205
     */
206 22
    public function instance(string $abstract, $instance)
207
    {
208 22
        if (isset($this->bind[$abstract])) {
209 19
            $bind = $this->bind[$abstract];
210
211 19
            if (is_string($bind)) {
212 19
                return $this->instance($bind, $instance);
213
            }
214
        }
215
216 22
        $this->instances[$abstract] = $instance;
217
218 22
        return $this;
219
    }
220
221
    /**
222
     * 判断容器中是否存在类及标识
223
     * @access public
224
     * @param string $abstract 类名或者标识
225
     * @return bool
226
     */
227 12
    public function bound(string $abstract): bool
228
    {
229 12
        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
230
    }
231
232
    /**
233
     * 判断容器中是否存在类及标识
234
     * @access public
235
     * @param string $name 类名或者标识
236
     * @return bool
237
     */
238 12
    public function has($name): bool
239
    {
240 12
        return $this->bound($name);
241
    }
242
243
    /**
244
     * 判断容器中是否存在对象实例
245
     * @access public
246
     * @param string $abstract 类名或者标识
247
     * @return bool
248
     */
249 4
    public function exists(string $abstract): bool
250
    {
251 4
        if (isset($this->bind[$abstract])) {
252 3
            $bind = $this->bind[$abstract];
253
254 3
            if (is_string($bind)) {
255 2
                return $this->exists($bind);
256
            }
257
        }
258
259 4
        return isset($this->instances[$abstract]);
260
    }
261
262
    /**
263
     * 创建类的实例 已经存在则直接获取
264
     * @access public
265
     * @param string $abstract    类名或者标识
266
     * @param array  $vars        变量
267
     * @param bool   $newInstance 是否每次创建新的实例
268
     * @return mixed
269
     */
270 16
    public function make(string $abstract, array $vars = [], bool $newInstance = false)
271
    {
272 16
        if (isset($this->instances[$abstract]) && !$newInstance) {
273 13
            return $this->instances[$abstract];
274
        }
275
276 15
        if (isset($this->bind[$abstract])) {
277 13
            $concrete = $this->bind[$abstract];
278
279 13
            if ($concrete instanceof Closure) {
280 3
                $object = $this->invokeFunction($concrete, $vars);
281
            } else {
282 13
                return $this->make($concrete, $vars, $newInstance);
283
            }
284
        } else {
285 12
            $object = $this->invokeClass($abstract, $vars);
286
        }
287
288 15
        if (!$newInstance) {
289 15
            $this->instances[$abstract] = $object;
290
        }
291
292 15
        return $object;
293
    }
294
295
    /**
296
     * 删除容器中的对象实例
297
     * @access public
298
     * @param string $name 类名或者标识
299
     * @return void
300
     */
301 2
    public function delete($name)
302
    {
303 2
        if (isset($this->bind[$name])) {
304 2
            $bind = $this->bind[$name];
305
306 2
            if (is_string($bind)) {
307 2
                $this->delete($bind);
308 2
                return;
309
            }
310
        }
311
312 2
        if (isset($this->instances[$name])) {
313 2
            unset($this->instances[$name]);
314
        }
315 2
    }
316
317
    /**
318
     * 执行函数或者闭包方法 支持参数调用
319
     * @access public
320
     * @param string|array|Closure $function 函数或者闭包
321
     * @param array $vars     参数
322
     * @return mixed
323
     */
324 6
    public function invokeFunction($function, array $vars = [])
325
    {
326
        try {
327 6
            $reflect = new ReflectionFunction($function);
328
329 5
            $args = $this->bindParams($reflect, $vars);
330
331 5
            if ($reflect->isClosure()) {
332
                // 解决在`php7.1`调用时会产生`$this`上下文不存在的错误 (https://bugs.php.net/bug.php?id=66430)
333 5
                return $function->__invoke(...$args);
334
            } else {
335
                return $reflect->invokeArgs($args);
336
            }
337 2
        } catch (ReflectionException $e) {
338
            // 如果是调用闭包时发生错误则尝试获取闭包的真实位置
339 2
            if (isset($reflect) && $reflect->isClosure() && $function instanceof Closure) {
340 1
                $function = "{Closure}@{$reflect->getFileName()}#L{$reflect->getStartLine()}-{$reflect->getEndLine()}";
341
            } else {
342 1
                $function .= '()';
343
            }
344 2
            throw new Exception('function not exists: ' . $function, 0, $e);
345
        }
346
    }
347
348
    /**
349
     * 调用反射执行类的方法 支持参数绑定
350
     * @access public
351
     * @param mixed $method 方法
352
     * @param array $vars   参数
353
     * @return mixed
354
     */
355 3
    public function invokeMethod($method, array $vars = [])
356
    {
357
        try {
358 3
            if (is_array($method)) {
359 3
                $class   = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]);
360 3
                $reflect = new ReflectionMethod($class, $method[1]);
361
            } else {
362
                // 静态方法
363 1
                $reflect = new ReflectionMethod($method);
0 ignored issues
show
Bug introduced by
The call to ReflectionMethod::__construct() has too few arguments starting with name. ( Ignorable by Annotation )

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

363
                $reflect = /** @scrutinizer ignore-call */ new ReflectionMethod($method);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
364
            }
365
366 2
            $args = $this->bindParams($reflect, $vars);
367
368 2
            return $reflect->invokeArgs($class ?? null, $args);
369 1
        } catch (ReflectionException $e) {
370 1
            if (is_array($method)) {
371 1
                $class    = is_object($method[0]) ? get_class($method[0]) : $method[0];
0 ignored issues
show
introduced by
The condition is_object($method[0]) is always false.
Loading history...
372 1
                $callback = $class . '::' . $method[1];
373
            } else {
374
                $callback = $method;
375
            }
376
377 1
            throw new Exception('method not exists: ' . $callback . '()', 0, $e);
378
        }
379
    }
380
381
    /**
382
     * 调用反射执行类的方法 支持参数绑定
383
     * @access public
384
     * @param object $instance 对象实例
385
     * @param mixed  $reflect  反射类
386
     * @param array  $vars     参数
387
     * @return mixed
388
     */
389 1
    public function invokeReflectMethod($instance, $reflect, array $vars = [])
390
    {
391 1
        $args = $this->bindParams($reflect, $vars);
392
393 1
        return $reflect->invokeArgs($instance, $args);
394
    }
395
396
    /**
397
     * 调用反射执行callable 支持参数绑定
398
     * @access public
399
     * @param mixed $callable
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
400
     * @param array $vars 参数
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
401
     * @return mixed
402
     */
403 2
    public function invoke($callable, array $vars = [])
404
    {
405 2
        if ($callable instanceof Closure) {
406 1
            return $this->invokeFunction($callable, $vars);
407
        }
408
409 2
        return $this->invokeMethod($callable, $vars);
410
    }
411
412
    /**
413
     * 调用反射执行类的实例化 支持依赖注入
414
     * @access public
415
     * @param string $class 类名
416
     * @param array  $vars  参数
417
     * @return mixed
418
     */
419 18
    public function invokeClass(string $class, array $vars = [])
420
    {
421
        try {
422 18
            $reflect = new ReflectionClass($class);
423
424 17
            if ($reflect->hasMethod('__make')) {
425 11
                $method = new ReflectionMethod($class, '__make');
426
427 11
                if ($method->isPublic() && $method->isStatic()) {
428 11
                    $args = $this->bindParams($method, $vars);
429 11
                    return $method->invokeArgs(null, $args);
430
                }
431
            }
432
433 14
            $constructor = $reflect->getConstructor();
434
435 14
            $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...
436
437 14
            $object = $reflect->newInstanceArgs($args);
438
439 14
            $this->invokeAfter($class, $object);
440
441 14
            return $object;
442 1
        } catch (ReflectionException $e) {
443 1
            throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
444
        }
445
    }
446
447
    /**
448
     * 执行invokeClass回调
449
     * @access protected
450
     * @param string $class  对象类名
451
     * @param object $object 容器对象实例
452
     * @return void
453
     */
454 14
    protected function invokeAfter(string $class, $object): void
455
    {
456 14
        if (isset($this->invokeCallback['*'])) {
457
            foreach ($this->invokeCallback['*'] as $callback) {
458
                $callback($object, $this);
459
            }
460
        }
461
462 14
        if (isset($this->invokeCallback[$class])) {
463
            foreach ($this->invokeCallback[$class] as $callback) {
464
                $callback($object, $this);
465
            }
466
        }
467 14
    }
468
469
    /**
470
     * 绑定参数
471
     * @access protected
472
     * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
473
     * @param array                                 $vars    参数
474
     * @return array
475
     */
476 21
    protected function bindParams($reflect, array $vars = []): array
477
    {
478 21
        if ($reflect->getNumberOfParameters() == 0) {
479 15
            return [];
480
        }
481
482
        // 判断数组类型 数字数组时按顺序绑定参数
483 14
        reset($vars);
484 14
        $type   = key($vars) === 0 ? 1 : 0;
485 14
        $params = $reflect->getParameters();
486 14
        $args   = [];
487
488 14
        foreach ($params as $param) {
489 14
            $name      = $param->getName();
490 14
            $lowerName = App::parseName($name);
491 14
            $class     = $param->getClass();
492
493 14
            if ($class) {
494 14
                $args[] = $this->getObjectParam($class->getName(), $vars);
495 12
            } elseif (1 == $type && !empty($vars)) {
496 4
                $args[] = array_shift($vars);
497 9
            } elseif (0 == $type && isset($vars[$name])) {
498 1
                $args[] = $vars[$name];
499 9
            } elseif (0 == $type && isset($vars[$lowerName])) {
500 1
                $args[] = $vars[$lowerName];
501 9
            } elseif ($param->isDefaultValueAvailable()) {
502 9
                $args[] = $param->getDefaultValue();
503
            } else {
504
                throw new InvalidArgumentException('method param miss:' . $name);
505
            }
506
        }
507
508 14
        return $args;
509
    }
510
511
    /**
512
     * 获取对象类型的参数值
513
     * @access protected
514
     * @param string $className 类名
515
     * @param array  $vars      参数
516
     * @return mixed
517
     */
518 14
    protected function getObjectParam(string $className, array &$vars)
519
    {
520 14
        $array = $vars;
521 14
        $value = array_shift($array);
522
523 14
        if ($value instanceof $className) {
524 1
            $result = $value;
525 1
            array_shift($vars);
526
        } else {
527 14
            $result = $this->make($className);
528
        }
529
530 14
        return $result;
531
    }
532
533 1
    public function __set($name, $value)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __set()
Loading history...
534
    {
535 1
        $this->bind($name, $value);
536 1
    }
537
538 14
    public function __get($name)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __get()
Loading history...
539
    {
540 14
        return $this->get($name);
541
    }
542
543 1
    public function __isset($name): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __isset()
Loading history...
544
    {
545 1
        return $this->exists($name);
546
    }
547
548 2
    public function __unset($name)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __unset()
Loading history...
549
    {
550 2
        $this->delete($name);
551 2
    }
552
553 1
    public function offsetExists($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetExists()
Loading history...
554
    {
555 1
        return $this->exists($key);
556
    }
557
558 1
    public function offsetGet($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetGet()
Loading history...
559
    {
560 1
        return $this->make($key);
561
    }
562
563 1
    public function offsetSet($key, $value)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetSet()
Loading history...
564
    {
565 1
        $this->bind($key, $value);
566 1
    }
567
568 1
    public function offsetUnset($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetUnset()
Loading history...
569
    {
570 1
        $this->delete($key);
571 1
    }
572
573
    //Countable
574
    public function count()
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
575
    {
576
        return count($this->instances);
577
    }
578
579
    //IteratorAggregate
580 1
    public function getIterator()
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
581
    {
582 1
        return new ArrayIterator($this->instances);
583
    }
584
}
585