Passed
Push — 6.0 ( af2f12...47ce62 )
by liu
05:13
created

Container::bindParams()   C

Complexity

Conditions 12
Paths 15

Size

Total Lines 33
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 12.0117

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 23
c 1
b 0
f 0
nc 15
nop 2
dl 0
loc 33
ccs 22
cts 23
cp 0.9565
crap 12.0117
rs 6.9666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
        if (isset($this->bind[$abstract])) {
103
            $abstract = $this->bind[$abstract];
104
        }
105
106
        $this->invokeCallback[$abstract][] = $callback;
107
    }
108
109
    /**
110
     * 获取容器中的对象实例 不存在则创建
111
     * @access public
112
     * @param string     $abstract    类名或者标识
113
     * @param array|true $vars        变量
114
     * @param bool       $newInstance 是否每次创建新的实例
115
     * @return object
116
     */
117 1
    public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
118
    {
119 1
        return static::getInstance()->make($abstract, $vars, $newInstance);
120
    }
121
122
    /**
123
     * 获取容器中的对象实例
124
     * @access public
125
     * @param string $abstract 类名或者标识
126
     * @return object
127
     */
128 15
    public function get($abstract)
129
    {
130 15
        if ($this->has($abstract)) {
131 14
            return $this->make($abstract);
132
        }
133
134 1
        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
     */
144 9
    public function bind($abstract, $concrete = null)
145
    {
146 9
        if (is_array($abstract)) {
147 4
            $this->bind = array_merge($this->bind, $abstract);
148 6
        } elseif ($concrete instanceof Closure) {
149 3
            $this->bind[$abstract] = $concrete;
150 4
        } elseif (is_object($concrete)) {
151 3
            $this->instance($abstract, $concrete);
152
        } else {
153 2
            $this->bind[$abstract] = $concrete;
154
        }
155
156 9
        return $this;
157
    }
158
159
    /**
160
     * 绑定一个类实例到容器
161
     * @access public
162
     * @param string $abstract 类名或者标识
163
     * @param object $instance 类的实例
164
     * @return $this
165
     */
166 20
    public function instance(string $abstract, $instance)
167
    {
168 20
        if (isset($this->bind[$abstract])) {
169 17
            $bind = $this->bind[$abstract];
170
171 17
            if (is_string($bind)) {
172 17
                return $this->instance($bind, $instance);
173
            }
174
        }
175
176 20
        $this->instances[$abstract] = $instance;
177
178 20
        return $this;
179
    }
180
181
    /**
182
     * 判断容器中是否存在类及标识
183
     * @access public
184
     * @param string $abstract 类名或者标识
185
     * @return bool
186
     */
187 15
    public function bound(string $abstract): bool
188
    {
189 15
        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
190
    }
191
192
    /**
193
     * 判断容器中是否存在类及标识
194
     * @access public
195
     * @param string $name 类名或者标识
196
     * @return bool
197
     */
198 15
    public function has($name): bool
199
    {
200 15
        return $this->bound($name);
201
    }
202
203
    /**
204
     * 判断容器中是否存在对象实例
205
     * @access public
206
     * @param string $abstract 类名或者标识
207
     * @return bool
208
     */
209 4
    public function exists(string $abstract): bool
210
    {
211 4
        if (isset($this->bind[$abstract])) {
212 3
            $bind = $this->bind[$abstract];
213
214 3
            if (is_string($bind)) {
215 2
                return $this->exists($bind);
216
            }
217
        }
218
219 4
        return isset($this->instances[$abstract]);
220
    }
221
222
    /**
223
     * 创建类的实例 已经存在则直接获取
224
     * @access public
225
     * @param string $abstract    类名或者标识
226
     * @param array  $vars        变量
227
     * @param bool   $newInstance 是否每次创建新的实例
228
     * @return mixed
229
     */
230 19
    public function make(string $abstract, array $vars = [], bool $newInstance = false)
231
    {
232 19
        if (isset($this->instances[$abstract]) && !$newInstance) {
233 13
            return $this->instances[$abstract];
234
        }
235
236 18
        if (isset($this->bind[$abstract])) {
237 16
            $concrete = $this->bind[$abstract];
238
239 16
            if ($concrete instanceof Closure) {
240 3
                $object = $this->invokeFunction($concrete, $vars);
241
            } else {
242 16
                return $this->make($concrete, $vars, $newInstance);
243
            }
244
        } else {
245 15
            $object = $this->invokeClass($abstract, $vars);
246
        }
247
248 18
        if (!$newInstance) {
249 18
            $this->instances[$abstract] = $object;
250
        }
251
252 18
        return $object;
253
    }
254
255
    /**
256
     * 删除容器中的对象实例
257
     * @access public
258
     * @param string $name 类名或者标识
259
     * @return void
260
     */
261 2
    public function delete($name)
262
    {
263 2
        if (isset($this->bind[$name])) {
264 2
            $bind = $this->bind[$name];
265
266 2
            if (is_string($bind)) {
267 2
                $this->delete($bind);
268 2
                return;
269
            }
270
        }
271
272 2
        if (isset($this->instances[$name])) {
273 2
            unset($this->instances[$name]);
274
        }
275 2
    }
276
277
    /**
278
     * 执行函数或者闭包方法 支持参数调用
279
     * @access public
280
     * @param string|array|Closure $function 函数或者闭包
281
     * @param array $vars     参数
282
     * @return mixed
283
     */
284 6
    public function invokeFunction($function, array $vars = [])
285
    {
286
        try {
287 6
            $reflect = new ReflectionFunction($function);
288
289 5
            $args = $this->bindParams($reflect, $vars);
290
291 5
            if ($reflect->isClosure()) {
292
                // 解决在`php7.1`调用时会产生`$this`上下文不存在的错误 (https://bugs.php.net/bug.php?id=66430)
293 5
                return $function->__invoke(...$args);
294
            } else {
295
                return $reflect->invokeArgs($args);
296
            }
297 2
        } catch (ReflectionException $e) {
298
            // 如果是调用闭包时发生错误则尝试获取闭包的真实位置
299 2
            if (isset($reflect) && $reflect->isClosure() && $function instanceof Closure) {
300 1
                $function = "{Closure}@{$reflect->getFileName()}#L{$reflect->getStartLine()}-{$reflect->getEndLine()}";
301
            } else {
302 1
                $function .= '()';
303
            }
304 2
            throw new Exception('function not exists: ' . $function, 0, $e);
305
        }
306
    }
307
308
    /**
309
     * 调用反射执行类的方法 支持参数绑定
310
     * @access public
311
     * @param mixed $method 方法
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
312
     * @param array $vars   参数
0 ignored issues
show
Coding Style introduced by
Expected 7 spaces after parameter name; 3 found
Loading history...
313
     * @param bool  $accessible 设置是否可访问
314
     * @return mixed
315
     */
316 3
    public function invokeMethod($method, array $vars = [], bool $accessible = false)
317
    {
318
        try {
319 3
            if (is_array($method)) {
320 3
                $class   = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]);
321 3
                $reflect = new ReflectionMethod($class, $method[1]);
322
            } else {
323
                // 静态方法
324 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

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