Completed
Push — 6.0 ( 487697...a3b118 )
by yun
08:06
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
            foreach ($abstract as $key => $val) {
146 2
                $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 13
            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
                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 21
    public function __get($name)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __get()
Loading history...
522
    {
523 21
        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