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

Container   F

Complexity

Total Complexity 82

Size/Duplication

Total Lines 529
Duplicated Lines 0 %

Test Coverage

Coverage 90.91%

Importance

Changes 0
Metric Value
eloc 174
dl 0
loc 529
ccs 160
cts 176
cp 0.9091
rs 2
c 0
b 0
f 0
wmc 82

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 __set() 0 3 1
C bindParams() 0 33 12
A invokeFunction() 0 16 5
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 delete() 0 13 4
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 mixed $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
            return $reflect->invokeArgs($args);
332 2
        } catch (ReflectionException $e) {
333
            // 如果是调用闭包时发生错误则尝试获取闭包的真实位置
334 2
            if (isset($reflect) && $reflect->isClosure() && $function instanceof Closure) {
335 1
                $function = "{Closure}@{$reflect->getFileName()}#L{$reflect->getStartLine()}-{$reflect->getEndLine()}";
336
            } else {
337 1
                $function .= '()';
338
            }
339 2
            throw new Exception('function not exists: ' . $function, 0, $e);
340
        }
341
    }
342
343
    /**
344
     * 调用反射执行类的方法 支持参数绑定
345
     * @access public
346
     * @param mixed $method 方法
347
     * @param array $vars   参数
348
     * @return mixed
349
     */
350 3
    public function invokeMethod($method, array $vars = [])
351
    {
352
        try {
353 3
            if (is_array($method)) {
354 3
                $class   = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]);
355 3
                $reflect = new ReflectionMethod($class, $method[1]);
356
            } else {
357
                // 静态方法
358 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

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