Container   F
last analyzed

Complexity

Total Complexity 85

Size/Duplication

Total Lines 525
Duplicated Lines 0 %

Test Coverage

Coverage 98.86%

Importance

Changes 12
Bugs 1 Features 0
Metric Value
eloc 156
c 12
b 1
f 0
dl 0
loc 525
ccs 174
cts 176
cp 0.9886
rs 2
wmc 85

32 Methods

Rating   Name   Duplication   Size   Complexity  
A __set() 0 3 1
A __isset() 0 3 1
A __unset() 0 3 1
A __get() 0 3 1
A setInstance() 0 3 1
A invokeFunction() 0 11 2
A invokeReflectMethod() 0 5 1
A invokeClass() 0 27 6
A invokeAfter() 0 11 5
A pull() 0 3 1
A bound() 0 3 2
A getInstance() 0 11 3
A resolving() 0 10 2
A get() 0 7 2
A has() 0 3 1
A make() 0 19 6
A delete() 0 6 2
B invokeMethod() 0 25 7
A bind() 0 18 6
A instance() 0 7 1
A getAlias() 0 11 3
A invoke() 0 8 4
A exists() 0 5 1
C bindParams() 0 35 15
A getObjectParam() 0 13 2
A factory() 0 5 2
A offsetExists() 0 4 1
A offsetGet() 0 4 1
A getIterator() 0 3 1
A offsetSet() 0 4 1
A offsetUnset() 0 4 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~2021 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
use Traversable;
31
32
/**
33
 * 容器管理类 支持PSR-11
34
 */
35
class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable
36
{
37
    /**
38
     * 容器对象实例
39
     * @var Container|Closure
40
     */
41
    protected static $instance;
42
43
    /**
44
     * 容器中的对象实例
45
     * @var array
46
     */
47
    protected $instances = [];
48
49
    /**
50
     * 容器绑定标识
51
     * @var array
52
     */
53
    protected $bind = [];
54
55
    /**
56
     * 容器回调
57
     * @var array
58
     */
59
    protected $invokeCallback = [];
60
61
    /**
62
     * 获取当前容器的实例(单例)
63
     * @access public
64
     * @return static
65 42
     */
66
    public static function getInstance()
67 42
    {
68 3
        if (is_null(static::$instance)) {
69
            static::$instance = new static;
70
        }
71 42
72 3
        if (static::$instance instanceof Closure) {
73
            return (static::$instance)();
74
        }
75 42
76
        return static::$instance;
77
    }
78
79
    /**
80
     * 设置当前容器的实例
81
     * @access public
82
     * @param object|Closure $instance
83
     * @return void
84 189
     */
85
    public static function setInstance($instance): void
86 189
    {
87 189
        static::$instance = $instance;
88
    }
89
90
    /**
91
     * 注册一个容器对象回调
92
     *
93
     * @param string|Closure $abstract
94
     * @param Closure|null   $callback
95
     * @return void
96 3
     */
97
    public function resolving($abstract, Closure $callback = null): void
98 3
    {
99 3
        if ($abstract instanceof Closure) {
100 3
            $this->invokeCallback['*'][] = $abstract;
101
            return;
102
        }
103 3
104
        $abstract = $this->getAlias($abstract);
105 3
106 3
        $this->invokeCallback[$abstract][] = $callback;
107
    }
108
109
    /**
110
     * 获取容器中的对象实例 不存在则创建
111
     * @template T
112
     * @param string|class-string<T> $abstract    类名或者标识
113
     * @param array                  $vars        变量
114
     * @param bool                   $newInstance 是否每次创建新的实例
115
     * @return T|object
116 3
     */
117
    public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
118 3
    {
119
        return static::getInstance()->make($abstract, $vars, $newInstance);
120
    }
121
122
    /**
123
     * 获取容器中的对象实例
124
     * @template T
125
     * @param string|class-string<T> $abstract 类名或者标识
126
     * @return T|object
127 60
     */
128
    public function get($abstract)
129 60
    {
130 57
        if ($this->has($abstract)) {
131
            return $this->make($abstract);
132
        }
133 3
134
        throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
135
    }
136
137
    /**
138
     * 绑定一个类、闭包、实例、接口实现到容器
139
     * @access public
140
     * @param string|array $abstract 类标识、接口
141
     * @param mixed        $concrete 要绑定的类、闭包或者实例
142
     * @return $this
143 27
     */
144
    public function bind($abstract, $concrete = null)
145 27
    {
146 9
        if (is_array($abstract)) {
147 7
            foreach ($abstract as $key => $val) {
148
                $this->bind($key, $val);
149 24
            }
150 9
        } elseif ($concrete instanceof Closure) {
151 18
            $this->bind[$abstract] = $concrete;
152 12
        } elseif (is_object($concrete)) {
153
            $this->instance($abstract, $concrete);
154 9
        } else {
155 9
            $abstract = $this->getAlias($abstract);
156 9
            if ($abstract != $concrete) {
157
                $this->bind[$abstract] = $concrete;
158
            }
159
        }
160 27
161
        return $this;
162
    }
163
164
    /**
165
     * 根据别名获取真实类名
166
     * @param  string $abstract
167
     * @return string
168 117
     */
169
    public function getAlias(string $abstract): string
170 117
    {
171 84
        if (isset($this->bind[$abstract])) {
172
            $bind = $this->bind[$abstract];
173 84
174 78
            if (is_string($bind)) {
175
                return $this->getAlias($bind);
176
            }
177
        }
178 117
179
        return $abstract;
180
    }
181
182
    /**
183
     * 绑定一个类实例到容器
184
     * @access public
185
     * @param string $abstract 类名或者标识
186
     * @param object $instance 类的实例
187
     * @return $this
188 45
     */
189
    public function instance(string $abstract, $instance)
190 45
    {
191
        $abstract = $this->getAlias($abstract);
192 45
193
        $this->instances[$abstract] = $instance;
194 45
195
        return $this;
196
    }
197
198
    /**
199
     * 判断容器中是否存在类及标识
200
     * @access public
201
     * @param string $abstract 类名或者标识
202
     * @return bool
203 60
     */
204
    public function bound(string $abstract): bool
205 60
    {
206
        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
207
    }
208
209
    /**
210
     * 判断容器中是否存在类及标识
211
     * @access public
212
     * @param string $name 类名或者标识
213
     * @return bool
214 60
     */
215
    public function has($name): bool
216 60
    {
217
        return $this->bound($name);
218
    }
219
220
    /**
221
     * 判断容器中是否存在对象实例
222
     * @access public
223
     * @param string $abstract 类名或者标识
224
     * @return bool
225 12
     */
226
    public function exists(string $abstract): bool
227 12
    {
228
        $abstract = $this->getAlias($abstract);
229 12
230
        return isset($this->instances[$abstract]);
231
    }
232
233
    /**
234
     * 创建类的实例 已经存在则直接获取
235
     * @template T
236
     * @param string|class-string<T> $abstract    类名或者标识
237
     * @param array                  $vars        变量
238
     * @param bool                   $newInstance 是否每次创建新的实例
239
     * @return T|object
240 93
     */
241
    public function make(string $abstract, array $vars = [], bool $newInstance = false)
242 93
    {
243
        $abstract = $this->getAlias($abstract);
244 93
245 48
        if (isset($this->instances[$abstract]) && !$newInstance) {
246
            return $this->instances[$abstract];
247
        }
248 87
249 9
        if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
250
            $object = $this->invokeFunction($this->bind[$abstract], $vars);
251 78
        } else {
252
            $object = $this->invokeClass($abstract, $vars);
253
        }
254 87
255 87
        if (!$newInstance) {
256
            $this->instances[$abstract] = $object;
257
        }
258 87
259
        return $object;
260
    }
261
262
    /**
263
     * 删除容器中的对象实例
264
     * @access public
265
     * @param string $name 类名或者标识
266
     * @return void
267 6
     */
268
    public function delete($name)
269 6
    {
270
        $name = $this->getAlias($name);
271 6
272 6
        if (isset($this->instances[$name])) {
273
            unset($this->instances[$name]);
274 6
        }
275
    }
276
277
    /**
278
     * 执行函数或者闭包方法 支持参数调用
279
     * @access public
280
     * @param string|Closure $function 函数或者闭包
281
     * @param array          $vars     参数
282
     * @return mixed
283 42
     */
284
    public function invokeFunction($function, array $vars = [])
285
    {
286 42
        try {
287 3
            $reflect = new ReflectionFunction($function);
288 3
        } catch (ReflectionException $e) {
289
            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

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