Completed
Branch 6.0 (d30585)
by yun
04:17
created

Container   F

Complexity

Total Complexity 81

Size/Duplication

Total Lines 518
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 98.84%

Importance

Changes 0
Metric Value
dl 0
loc 518
rs 2
c 0
b 0
f 0
ccs 171
cts 173
cp 0.9884
wmc 81
lcom 1
cbo 3

32 Methods

Rating   Name   Duplication   Size   Complexity  
A getInstance() 0 12 3
A setInstance() 0 4 1
A resolving() 0 11 2
A pull() 0 4 1
A get() 0 8 2
A bind() 0 18 5
A getAlias() 0 12 3
A instance() 0 8 1
A bound() 0 4 2
A has() 0 4 1
A exists() 0 6 1
B make() 0 20 6
A delete() 0 8 2
A invokeFunction() 0 12 2
B invokeMethod() 0 26 7
A invokeReflectMethod() 0 6 1
A invoke() 0 10 4
B invokeClass() 0 26 6
A invokeAfter() 0 14 5
C bindParams() 0 34 12
A factory() 0 6 2
A getObjectParam() 0 14 2
A __set() 0 4 1
A __get() 0 4 1
A __isset() 0 4 1
A __unset() 0 4 1
A offsetExists() 0 4 1
A offsetGet() 0 4 1
A offsetSet() 0 4 1
A offsetUnset() 0 4 1
A count() 0 4 1
A getIterator() 0 4 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 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 27
    public static function getInstance()
66
    {
67 27
        if (is_null(static::$instance)) {
68 3
            static::$instance = new static;
69
        }
70
71 27
        if (static::$instance instanceof Closure) {
72 3
            return (static::$instance)();
73
        }
74
75 27
        return static::$instance;
76
    }
77
78
    /**
79
     * 设置当前容器的实例
80
     * @access public
81
     * @param object|Closure $instance
82
     * @return void
83
     */
84 174
    public static function setInstance($instance): void
85
    {
86 174
        static::$instance = $instance;
87 174
    }
88
89
    /**
90
     * 注册一个容器对象回调
91
     *
92
     * @param string|Closure $abstract
93
     * @param Closure|null   $callback
94
     * @return void
95
     */
96 3
    public function resolving($abstract, Closure $callback = null): void
97
    {
98 3
        if ($abstract instanceof Closure) {
99 3
            $this->invokeCallback['*'][] = $abstract;
100 3
            return;
101
        }
102
103 3
        $abstract = $this->getAlias($abstract);
104
105 3
        $this->invokeCallback[$abstract][] = $callback;
106 3
    }
107
108
    /**
109
     * 获取容器中的对象实例 不存在则创建
110
     * @access public
111
     * @param string     $abstract    类名或者标识
112
     * @param array|true $vars        变量
113
     * @param bool       $newInstance 是否每次创建新的实例
114
     * @return object
115
     */
116 3
    public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
117
    {
118 3
        return static::getInstance()->make($abstract, $vars, $newInstance);
119
    }
120
121
    /**
122
     * 获取容器中的对象实例
123
     * @access public
124
     * @param string $abstract 类名或者标识
125
     * @return object
126
     */
127 45
    public function get($abstract)
128
    {
129 45
        if ($this->has($abstract)) {
130 42
            return $this->make($abstract);
131
        }
132
133 3
        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 27
    public function bind($abstract, $concrete = null)
144
    {
145 27
        if (is_array($abstract)) {
146 9
            foreach ($abstract as $key => $val) {
147 7
                $this->bind($key, $val);
148
            }
149 24
        } elseif ($concrete instanceof Closure) {
150 9
            $this->bind[$abstract] = $concrete;
151 18
        } elseif (is_object($concrete)) {
152 12
            $this->instance($abstract, $concrete);
153
        } else {
154 9
            $abstract = $this->getAlias($abstract);
155
156 9
            $this->bind[$abstract] = $concrete;
157
        }
158
159 27
        return $this;
160
    }
161
162
    /**
163
     * 根据别名获取真实类名
164
     * @param  string $abstract
165
     * @return string
166
     */
167 102
    public function getAlias(string $abstract): string
168
    {
169 102
        if (isset($this->bind[$abstract])) {
170 69
            $bind = $this->bind[$abstract];
171
172 69
            if (is_string($bind)) {
173 63
                return $this->getAlias($bind);
174
            }
175
        }
176
177 102
        return $abstract;
178
    }
179
180
    /**
181
     * 绑定一个类实例到容器
182
     * @access public
183
     * @param string $abstract 类名或者标识
184
     * @param object $instance 类的实例
185
     * @return $this
186
     */
187 45
    public function instance(string $abstract, $instance)
188
    {
189 45
        $abstract = $this->getAlias($abstract);
190
191 45
        $this->instances[$abstract] = $instance;
192
193 45
        return $this;
194
    }
195
196
    /**
197
     * 判断容器中是否存在类及标识
198
     * @access public
199
     * @param string $abstract 类名或者标识
200
     * @return bool
201
     */
202 45
    public function bound(string $abstract): bool
203
    {
204 45
        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 45
    public function has($name): bool
214
    {
215 45
        return $this->bound($name);
216
    }
217
218
    /**
219
     * 判断容器中是否存在对象实例
220
     * @access public
221
     * @param string $abstract 类名或者标识
222
     * @return bool
223
     */
224 12
    public function exists(string $abstract): bool
225
    {
226 12
        $abstract = $this->getAlias($abstract);
227
228 12
        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 78
    public function make(string $abstract, array $vars = [], bool $newInstance = false)
240
    {
241 78
        $abstract = $this->getAlias($abstract);
242
243 78
        if (isset($this->instances[$abstract]) && !$newInstance) {
244 45
            return $this->instances[$abstract];
245
        }
246
247 72
        if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
248 9
            $object = $this->invokeFunction($this->bind[$abstract], $vars);
249
        } else {
250 63
            $object = $this->invokeClass($abstract, $vars);
251
        }
252
253 72
        if (!$newInstance) {
254 72
            $this->instances[$abstract] = $object;
255
        }
256
257 72
        return $object;
258
    }
259
260
    /**
261
     * 删除容器中的对象实例
262
     * @access public
263
     * @param string $name 类名或者标识
264
     * @return void
265
     */
266 6
    public function delete($name)
267
    {
268 6
        $name = $this->getAlias($name);
269
270 6
        if (isset($this->instances[$name])) {
271 6
            unset($this->instances[$name]);
272
        }
273 6
    }
274
275
    /**
276
     * 执行函数或者闭包方法 支持参数调用
277
     * @access public
278
     * @param string|Closure $function 函数或者闭包
279
     * @param array          $vars     参数
280
     * @return mixed
281
     */
282 30
    public function invokeFunction($function, array $vars = [])
283
    {
284
        try {
285 30
            $reflect = new ReflectionFunction($function);
286 3
        } catch (ReflectionException $e) {
287 3
            throw new FuncNotFoundException("function not exists: {$function}()", $function, $e);
0 ignored issues
show
Bug introduced by
It seems like $function defined by parameter $function on line 282 can also be of type object<Closure>; however, think\exception\FuncNotF...xception::__construct() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
288
        }
289
290 27
        $args = $this->bindParams($reflect, $vars);
291
292 27
        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 18
    public function invokeMethod($method, array $vars = [], bool $accessible = false)
304
    {
305 18
        if (is_array($method)) {
306 18
            [$class, $method] = $method;
0 ignored issues
show
Bug introduced by
The variable $class seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
307
308 18
            $class = is_object($class) ? $class : $this->invokeClass($class);
0 ignored issues
show
Bug introduced by
The variable $class seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
309
        } else {
310
            // 静态方法
311 3
            [$class, $method] = explode('::', $method);
0 ignored issues
show
Bug introduced by
The variable $class seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
312
        }
313
314
        try {
315 18
            $reflect = new ReflectionMethod($class, $method);
0 ignored issues
show
Bug introduced by
The variable $class does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
316 3
        } catch (ReflectionException $e) {
317 3
            $class = is_object($class) ? get_class($class) : $class;
318 3
            throw new FuncNotFoundException('method not exists: ' . $class . '::' . $method . '()', "{$class}::{$method}", $e);
319
        }
320
321 15
        $args = $this->bindParams($reflect, $vars);
322
323 15
        if ($accessible) {
324 3
            $reflect->setAccessible($accessible);
325
        }
326
327 15
        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 12
    public function invokeReflectMethod($instance, $reflect, array $vars = [])
339
    {
340 12
        $args = $this->bindParams($reflect, $vars);
341
342 12
        return $reflect->invokeArgs($instance, $args);
343
    }
344
345
    /**
346
     * 调用反射执行callable 支持参数绑定
347
     * @access public
348
     * @param mixed $callable
349
     * @param array $vars       参数
350
     * @param bool  $accessible 设置是否可访问
351
     * @return mixed
352
     */
353 24
    public function invoke($callable, array $vars = [], bool $accessible = false)
354
    {
355 24
        if ($callable instanceof Closure) {
356 15
            return $this->invokeFunction($callable, $vars);
357 12
        } elseif (is_string($callable) && false === strpos($callable, '::')) {
358 3
            return $this->invokeFunction($callable, $vars);
359
        } else {
360 12
            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 105
    public function invokeClass(string $class, array $vars = [])
372
    {
373
        try {
374 105
            $reflect = new ReflectionClass($class);
375 6
        } catch (ReflectionException $e) {
376 6
            throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
377
        }
378
379 102
        if ($reflect->hasMethod('__make')) {
380 33
            $method = $reflect->getMethod('__make');
381 33
            if ($method->isPublic() && $method->isStatic()) {
382 33
                $args = $this->bindParams($method, $vars);
383 33
                return $method->invokeArgs(null, $args);
384
            }
385
        }
386
387 90
        $constructor = $reflect->getConstructor();
388
389 90
        $args = $constructor ? $this->bindParams($constructor, $vars) : [];
390
391 90
        $object = $reflect->newInstanceArgs($args);
392
393 90
        $this->invokeAfter($class, $object);
394
395 90
        return $object;
396
    }
397
398
    /**
399
     * 执行invokeClass回调
400
     * @access protected
401
     * @param string $class  对象类名
402
     * @param object $object 容器对象实例
403
     * @return void
404
     */
405 90
    protected function invokeAfter(string $class, $object): void
406
    {
407 90
        if (isset($this->invokeCallback['*'])) {
408 3
            foreach ($this->invokeCallback['*'] as $callback) {
409 3
                $callback($object, $this);
410
            }
411
        }
412
413 90
        if (isset($this->invokeCallback[$class])) {
414 3
            foreach ($this->invokeCallback[$class] as $callback) {
415 3
                $callback($object, $this);
416
            }
417
        }
418 90
    }
419
420
    /**
421
     * 绑定参数
422
     * @access protected
423
     * @param ReflectionFunctionAbstract $reflect 反射类
424
     * @param array                      $vars    参数
425
     * @return array
426
     */
427 114
    protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array
428
    {
429 114
        if ($reflect->getNumberOfParameters() == 0) {
430 66
            return [];
431
        }
432
433
        // 判断数组类型 数字数组时按顺序绑定参数
434 66
        reset($vars);
435 66
        $type   = key($vars) === 0 ? 1 : 0;
436 66
        $params = $reflect->getParameters();
437 66
        $args   = [];
438
439 66
        foreach ($params as $param) {
440 66
            $name      = $param->getName();
0 ignored issues
show
Bug introduced by
Consider using $param->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
441 66
            $lowerName = Str::snake($name);
442 66
            $class     = $param->getClass();
443
444 66
            if ($class) {
445 60
                $args[] = $this->getObjectParam($class->getName(), $vars);
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
446 57
            } elseif (1 == $type && !empty($vars)) {
447 51
                $args[] = array_shift($vars);
448 9
            } elseif (0 == $type && isset($vars[$name])) {
449 3
                $args[] = $vars[$name];
450 9
            } elseif (0 == $type && isset($vars[$lowerName])) {
451 3
                $args[] = $vars[$lowerName];
452 9
            } elseif ($param->isDefaultValueAvailable()) {
453 9
                $args[] = $param->getDefaultValue();
454
            } else {
455 22
                throw new InvalidArgumentException('method param miss:' . $name);
456
            }
457
        }
458
459 66
        return $args;
460
    }
461
462
    /**
463
     * 创建工厂对象实例
464
     * @param string $name      工厂类名
465
     * @param string $namespace 默认命名空间
466
     * @param array  $args
467
     * @return mixed
468
     * @deprecated
469
     * @access public
470
     */
471 3
    public static function factory(string $name, string $namespace = '', ...$args)
472
    {
473 3
        $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name);
474
475 3
        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 60
    protected function getObjectParam(string $className, array &$vars)
486
    {
487 60
        $array = $vars;
488 60
        $value = array_shift($array);
489
490 60
        if ($value instanceof $className) {
491 3
            $result = $value;
492 3
            array_shift($vars);
493
        } else {
494 60
            $result = $this->make($className);
495
        }
496
497 60
        return $result;
498
    }
499
500 3
    public function __set($name, $value)
501
    {
502 3
        $this->bind($name, $value);
503 3
    }
504
505 87
    public function __get($name)
506
    {
507 87
        return $this->get($name);
508
    }
509
510 3
    public function __isset($name): bool
511
    {
512 3
        return $this->exists($name);
513
    }
514
515 6
    public function __unset($name)
516
    {
517 6
        $this->delete($name);
518 6
    }
519
520 3
    public function offsetExists($key)
521
    {
522 3
        return $this->exists($key);
523
    }
524
525 3
    public function offsetGet($key)
526
    {
527 3
        return $this->make($key);
528
    }
529
530 3
    public function offsetSet($key, $value)
531
    {
532 3
        $this->bind($key, $value);
533 3
    }
534
535 3
    public function offsetUnset($key)
536
    {
537 3
        $this->delete($key);
538 3
    }
539
540
    //Countable
541
    public function count()
542
    {
543
        return count($this->instances);
544
    }
545
546
    //IteratorAggregate
547 3
    public function getIterator()
548
    {
549 3
        return new ArrayIterator($this->instances);
550
    }
551
}
552