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

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