Completed
Pull Request — 6.0 (#1965)
by nhzex
04:47
created

Container   F

Complexity

Total Complexity 84

Size/Duplication

Total Lines 533
Duplicated Lines 0 %

Test Coverage

Coverage 91.11%

Importance

Changes 9
Bugs 0 Features 0
Metric Value
eloc 156
c 9
b 0
f 0
dl 0
loc 533
ccs 164
cts 180
cp 0.9111
rs 2
wmc 84

32 Methods

Rating   Name   Duplication   Size   Complexity  
A setInstance() 0 3 1
A getInstance() 0 11 3
A pull() 0 3 1
A resolving() 0 10 2
A get() 0 7 2
A __set() 0 3 1
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 __unset() 0 3 1
A getObjectParam() 0 13 2
A factory() 0 9 3
A bound() 0 3 2
A has() 0 3 1
A make() 0 19 6
B invokeMethod() 0 27 7
A offsetExists() 0 3 1
A bind() 0 17 5
A offsetGet() 0 3 1
A instance() 0 7 1
A getAlias() 0 11 3
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 exists() 0 5 1
A count() 0 3 1
A delete() 0 6 2
C bindParams() 0 33 12

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 Exception;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, think\Exception. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
20
use InvalidArgumentException;
21
use IteratorAggregate;
22
use Psr\Container\ContainerInterface;
23
use ReflectionClass;
24
use ReflectionException;
25
use ReflectionFunction;
26
use ReflectionMethod;
27
use think\exception\ClassNotFoundException;
28
use think\helper\Str;
29
30
/**
31
 * 容器管理类 支持PSR-11
32
 */
33
class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable
34
{
35
    /**
36
     * 容器对象实例
37
     * @var Container|Closure
38
     */
39
    protected static $instance;
40
41
    /**
42
     * 容器中的对象实例
43
     * @var array
44
     */
45
    protected $instances = [];
46
47
    /**
48
     * 容器绑定标识
49
     * @var array
50
     */
51
    protected $bind = [];
52
53
    /**
54
     * 容器回调
55
     * @var array
56
     */
57
    protected $invokeCallback = [];
58
59
    /**
60
     * 获取当前容器的实例(单例)
61
     * @access public
62
     * @return static
63
     */
64 6
    public static function getInstance()
65
    {
66 6
        if (is_null(static::$instance)) {
67 1
            static::$instance = new static;
68
        }
69
70 6
        if (static::$instance instanceof Closure) {
71 1
            return (static::$instance)();
72
        }
73
74 6
        return static::$instance;
75
    }
76
77
    /**
78
     * 设置当前容器的实例
79
     * @access public
80
     * @param object|Closure $instance
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
81
     * @return void
82
     */
83 40
    public static function setInstance($instance): void
84
    {
85 40
        static::$instance = $instance;
86 40
    }
87
88
    /**
89
     * 注册一个容器对象回调
90
     *
91
     * @param string|Closure $abstract
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
92
     * @param Closure|null   $callback
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
93
     * @return void
94
     */
95
    public function resolving($abstract, Closure $callback = null): void
96
    {
97
        if ($abstract instanceof Closure) {
98
            $this->invokeCallback['*'][] = $abstract;
99
            return;
100
        }
101
102
        $abstract = $this->getAlias($abstract);
103
104
        $this->invokeCallback[$abstract][] = $callback;
105
    }
106
107
    /**
108
     * 获取容器中的对象实例 不存在则创建
109
     * @access public
110
     * @param string     $abstract    类名或者标识
111
     * @param array|true $vars        变量
112
     * @param bool       $newInstance 是否每次创建新的实例
113
     * @return object
114
     */
115 1
    public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
116
    {
117 1
        return static::getInstance()->make($abstract, $vars, $newInstance);
118
    }
119
120
    /**
121
     * 获取容器中的对象实例
122
     * @access public
123
     * @param string $abstract 类名或者标识
124
     * @return object
125
     */
126 15
    public function get($abstract)
127
    {
128 15
        if ($this->has($abstract)) {
129 14
            return $this->make($abstract);
130
        }
131
132 1
        throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
133
    }
134
135
    /**
136
     * 绑定一个类、闭包、实例、接口实现到容器
137
     * @access public
138
     * @param string|array $abstract 类标识、接口
139
     * @param mixed        $concrete 要绑定的类、闭包或者实例
140
     * @return $this
141
     */
142 9
    public function bind($abstract, $concrete = null)
143
    {
144 9
        if (is_array($abstract)) {
145 4
            foreach ($abstract as $key => $val) {
146 4
                $this->bind($key, $val);
147
            }
148 7
        } elseif ($concrete instanceof Closure) {
149 3
            $this->bind[$abstract] = $concrete;
150 5
        } elseif (is_object($concrete)) {
151 3
            $this->instance($abstract, $concrete);
152
        } else {
153 3
            $abstract = $this->getAlias($abstract);
154
155 3
            $this->bind[$abstract] = $concrete;
156
        }
157
158 9
        return $this;
159
    }
160
161
    /**
162
     * 根据别名获取真实类名
163
     * @param  string $abstract
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
164
     * @return string
165
     */
166 27
    public function getAlias(string $abstract): string
167
    {
168 27
        if (isset($this->bind[$abstract])) {
169 23
            $bind = $this->bind[$abstract];
170
171 23
            if (is_string($bind)) {
172 21
                return $this->getAlias($bind);
173
            }
174
        }
175
176 27
        return $abstract;
177
    }
178
179
    /**
180
     * 绑定一个类实例到容器
181
     * @access public
182
     * @param string $abstract 类名或者标识
183
     * @param object $instance 类的实例
184
     * @return $this
185
     */
186 20
    public function instance(string $abstract, $instance)
187
    {
188 20
        $abstract = $this->getAlias($abstract);
189
190 20
        $this->instances[$abstract] = $instance;
191
192 20
        return $this;
193
    }
194
195
    /**
196
     * 判断容器中是否存在类及标识
197
     * @access public
198
     * @param string $abstract 类名或者标识
199
     * @return bool
200
     */
201 15
    public function bound(string $abstract): bool
202
    {
203 15
        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
204
    }
205
206
    /**
207
     * 判断容器中是否存在类及标识
208
     * @access public
209
     * @param string $name 类名或者标识
210
     * @return bool
211
     */
212 15
    public function has($name): bool
213
    {
214 15
        return $this->bound($name);
215
    }
216
217
    /**
218
     * 判断容器中是否存在对象实例
219
     * @access public
220
     * @param string $abstract 类名或者标识
221
     * @return bool
222
     */
223 4
    public function exists(string $abstract): bool
224
    {
225 4
        $abstract = $this->getAlias($abstract);
226
227 4
        return isset($this->instances[$abstract]);
228
    }
229
230
    /**
231
     * 创建类的实例 已经存在则直接获取
232
     * @access public
233
     * @param string $abstract    类名或者标识
234
     * @param array  $vars        变量
235
     * @param bool   $newInstance 是否每次创建新的实例
236
     * @return mixed
237
     */
238 19
    public function make(string $abstract, array $vars = [], bool $newInstance = false)
239
    {
240 19
        $abstract = $this->getAlias($abstract);
241
242 19
        if (isset($this->instances[$abstract]) && !$newInstance) {
243 13
            return $this->instances[$abstract];
244
        }
245
246 18
        if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
247 3
            $object = $this->invokeFunction($this->bind[$abstract], $vars);
248
        } else {
249 15
            $object = $this->invokeClass($abstract, $vars);
250
        }
251
252 18
        if (!$newInstance) {
253 18
            $this->instances[$abstract] = $object;
254
        }
255
256 18
        return $object;
257
    }
258
259
    /**
260
     * 删除容器中的对象实例
261
     * @access public
262
     * @param string $name 类名或者标识
263
     * @return void
264
     */
265 2
    public function delete($name)
266
    {
267 2
        $name = $this->getAlias($name);
268
269 2
        if (isset($this->instances[$name])) {
270 2
            unset($this->instances[$name]);
271
        }
272 2
    }
273
274
    /**
275
     * 执行函数或者闭包方法 支持参数调用
276
     * @access public
277
     * @param string|array|Closure $function 函数或者闭包
278
     * @param array                $vars     参数
279
     * @return mixed
280
     */
281 6
    public function invokeFunction($function, array $vars = [])
282
    {
283
        try {
284 6
            $reflect = new ReflectionFunction($function);
285
286 5
            $args = $this->bindParams($reflect, $vars);
287
288 5
            if ($reflect->isClosure()) {
289
                // 解决在`php7.1`调用时会产生`$this`上下文不存在的错误 (https://bugs.php.net/bug.php?id=66430)
290 5
                return $function->__invoke(...$args);
291
            } else {
292
                return $reflect->invokeArgs($args);
293
            }
294 2
        } catch (ReflectionException $e) {
295
            // 如果是调用闭包时发生错误则尝试获取闭包的真实位置
296 2
            if (isset($reflect) && $reflect->isClosure() && $function instanceof Closure) {
297 1
                $function = "{Closure}@{$reflect->getFileName()}#L{$reflect->getStartLine()}-{$reflect->getEndLine()}";
298
            } else {
299 1
                $function .= '()';
300
            }
301 2
            throw new Exception('function not exists: ' . $function, 0, $e);
302
        }
303
    }
304
305
    /**
306
     * 调用反射执行类的方法 支持参数绑定
307
     * @access public
308
     * @param mixed $method     方法
309
     * @param array $vars       参数
310
     * @param bool  $accessible 设置是否可访问
311
     * @return mixed
312
     */
313 3
    public function invokeMethod($method, array $vars = [], bool $accessible = false)
314
    {
315
        try {
316 3
            if (is_array($method)) {
317 3
                $class   = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]);
318 3
                $reflect = new ReflectionMethod($class, $method[1]);
319
            } else {
320
                // 静态方法
321 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

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