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

323
                $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...
324
            }
325
326 2
            $args = $this->bindParams($reflect, $vars);
327
328 2
            if ($accessible) {
329
                $reflect->setAccessible($accessible);
330
            }
331
332 2
            return $reflect->invokeArgs($class ?? null, $args);
333 1
        } catch (ReflectionException $e) {
334 1
            if (is_array($method)) {
335 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...
336 1
                $callback = $class . '::' . $method[1];
337
            } else {
338
                $callback = $method;
339
            }
340
341 1
            throw new Exception('method not exists: ' . $callback . '()', 0, $e);
342
        }
343
    }
344
345
    /**
346
     * 调用反射执行类的方法 支持参数绑定
347
     * @access public
348
     * @param object $instance 对象实例
349
     * @param mixed  $reflect  反射类
350
     * @param array  $vars     参数
351
     * @return mixed
352
     */
353 1
    public function invokeReflectMethod($instance, $reflect, array $vars = [])
354
    {
355 1
        $args = $this->bindParams($reflect, $vars);
356
357 1
        return $reflect->invokeArgs($instance, $args);
358
    }
359
360
    /**
361
     * 调用反射执行callable 支持参数绑定
362
     * @access public
363
     * @param mixed $callable
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
364
     * @param array $vars 参数
0 ignored issues
show
Coding Style introduced by
Expected 7 spaces after parameter name; 1 found
Loading history...
365
     * @param bool  $accessible 设置是否可访问
366
     * @return mixed
367
     */
368 2
    public function invoke($callable, array $vars = [], bool $accessible = false)
369
    {
370 2
        if ($callable instanceof Closure) {
371 1
            return $this->invokeFunction($callable, $vars);
372
        }
373
374 2
        return $this->invokeMethod($callable, $vars, $accessible);
375
    }
376
377
    /**
378
     * 调用反射执行类的实例化 支持依赖注入
379
     * @access public
380
     * @param string $class 类名
381
     * @param array  $vars  参数
382
     * @return mixed
383
     */
384 24
    public function invokeClass(string $class, array $vars = [])
385
    {
386
        try {
387 24
            $reflect = new ReflectionClass($class);
388
389 23
            if ($reflect->hasMethod('__make')) {
390 11
                $method = new ReflectionMethod($class, '__make');
391
392 11
                if ($method->isPublic() && $method->isStatic()) {
393 11
                    $args = $this->bindParams($method, $vars);
394 11
                    return $method->invokeArgs(null, $args);
395
                }
396
            }
397
398 20
            $constructor = $reflect->getConstructor();
399
400 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...
401
402 20
            $object = $reflect->newInstanceArgs($args);
403
404 20
            $this->invokeAfter($class, $object);
405
406 20
            return $object;
407 1
        } catch (ReflectionException $e) {
408 1
            throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
409
        }
410
    }
411
412
    /**
413
     * 执行invokeClass回调
414
     * @access protected
415
     * @param string $class  对象类名
416
     * @param object $object 容器对象实例
417
     * @return void
418
     */
419 20
    protected function invokeAfter(string $class, $object): void
420
    {
421 20
        if (isset($this->invokeCallback['*'])) {
422
            foreach ($this->invokeCallback['*'] as $callback) {
423
                $callback($object, $this);
424
            }
425
        }
426
427 20
        if (isset($this->invokeCallback[$class])) {
428
            foreach ($this->invokeCallback[$class] as $callback) {
429
                $callback($object, $this);
430
            }
431
        }
432 20
    }
433
434
    /**
435
     * 绑定参数
436
     * @access protected
437
     * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
438
     * @param array                                 $vars    参数
439
     * @return array
440
     */
441 27
    protected function bindParams($reflect, array $vars = []): array
442
    {
443 27
        if ($reflect->getNumberOfParameters() == 0) {
444 15
            return [];
445
        }
446
447
        // 判断数组类型 数字数组时按顺序绑定参数
448 20
        reset($vars);
449 20
        $type   = key($vars) === 0 ? 1 : 0;
450 20
        $params = $reflect->getParameters();
451 20
        $args   = [];
452
453 20
        foreach ($params as $param) {
454 20
            $name      = $param->getName();
455 20
            $lowerName = self::parseName($name);
0 ignored issues
show
Deprecated Code introduced by
The function think\Container::parseName() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

455
            $lowerName = /** @scrutinizer ignore-deprecated */ self::parseName($name);
Loading history...
456 20
            $class     = $param->getClass();
457
458 20
            if ($class) {
459 19
                $args[] = $this->getObjectParam($class->getName(), $vars);
460 18
            } elseif (1 == $type && !empty($vars)) {
461 10
                $args[] = array_shift($vars);
462 9
            } elseif (0 == $type && isset($vars[$name])) {
463 1
                $args[] = $vars[$name];
464 9
            } elseif (0 == $type && isset($vars[$lowerName])) {
465 1
                $args[] = $vars[$lowerName];
466 9
            } elseif ($param->isDefaultValueAvailable()) {
467 9
                $args[] = $param->getDefaultValue();
468
            } else {
469
                throw new InvalidArgumentException('method param miss:' . $name);
470
            }
471
        }
472
473 20
        return $args;
474
    }
475
476
    /**
477
     * 字符串命名风格转换
478
     * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
479
     * @deprecated
480
     * @access public
481
     * @param string  $name    字符串
482
     * @param integer $type    转换类型
483
     * @param bool    $ucfirst 首字母是否大写(驼峰规则)
484
     * @return string
485
     */
486 21
    public static function parseName(string $name = null, int $type = 0, bool $ucfirst = true): string
487
    {
488 21
        if ($type) {
489
            $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
490 1
                return strtoupper($match[1]);
491 1
            }, $name);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
492 1
            return $ucfirst ? ucfirst($name) : lcfirst($name);
493
        }
494
495 21
        return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
496
    }
497
498
    /**
499
     * 获取类名(不包含命名空间)
500
     * @deprecated
501
     * @access public
502
     * @param string|object $class
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
503
     * @return string
504
     */
505 1
    public static function classBaseName($class): string
506
    {
507 1
        $class = is_object($class) ? get_class($class) : $class;
508 1
        return basename(str_replace('\\', '/', $class));
509
    }
510
511
    /**
0 ignored issues
show
Coding Style introduced by
Parameter ...$args should have a doc-comment as per coding-style.
Loading history...
512
     * 创建工厂对象实例
513
     * @deprecated
514
     * @access public
515
     * @param string $name      工厂类名
516
     * @param string $namespace 默认命名空间
517
     * @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...
518
     * @return mixed
519
     */
520 4
    public static function factory(string $name, string $namespace = '', ...$args)
521
    {
522 4
        $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name);
523
524 4
        if (class_exists($class)) {
525 4
            return Container::getInstance()->invokeClass($class, $args);
526
        }
527
528 1
        throw new ClassNotFoundException('class not exists:' . $class, $class);
529
    }
530
531
    /**
532
     * 获取对象类型的参数值
533
     * @access protected
534
     * @param string $className 类名
535
     * @param array  $vars      参数
536
     * @return mixed
537
     */
538 19
    protected function getObjectParam(string $className, array &$vars)
539
    {
540 19
        $array = $vars;
541 19
        $value = array_shift($array);
542
543 19
        if ($value instanceof $className) {
544 1
            $result = $value;
545 1
            array_shift($vars);
546
        } else {
547 19
            $result = $this->make($className);
548
        }
549
550 19
        return $result;
551
    }
552
553 1
    public function __set($name, $value)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __set()
Loading history...
554
    {
555 1
        $this->bind($name, $value);
556 1
    }
557
558 22
    public function __get($name)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __get()
Loading history...
559
    {
560 22
        return $this->get($name);
561
    }
562
563 1
    public function __isset($name): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __isset()
Loading history...
564
    {
565 1
        return $this->exists($name);
566
    }
567
568 2
    public function __unset($name)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __unset()
Loading history...
569
    {
570 2
        $this->delete($name);
571 2
    }
572
573 1
    public function offsetExists($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetExists()
Loading history...
574
    {
575 1
        return $this->exists($key);
576
    }
577
578 1
    public function offsetGet($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetGet()
Loading history...
579
    {
580 1
        return $this->make($key);
581
    }
582
583 1
    public function offsetSet($key, $value)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetSet()
Loading history...
584
    {
585 1
        $this->bind($key, $value);
586 1
    }
587
588 1
    public function offsetUnset($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetUnset()
Loading history...
589
    {
590 1
        $this->delete($key);
591 1
    }
592
593
    //Countable
594
    public function count()
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
595
    {
596
        return count($this->instances);
597
    }
598
599
    //IteratorAggregate
600 1
    public function getIterator()
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
601
    {
602 1
        return new ArrayIterator($this->instances);
603
    }
604
}
605