Completed
Push — 6.0 ( 973f63...cba287 )
by liu
05:40
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 方法
311
     * @param array $vars   参数
312
     * @return mixed
313
     */
314 3
    public function invokeMethod($method, array $vars = [])
315
    {
316
        try {
317 3
            if (is_array($method)) {
318 3
                $class   = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]);
319 3
                $reflect = new ReflectionMethod($class, $method[1]);
320
            } else {
321
                // 静态方法
322 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

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

449
            $lowerName = /** @scrutinizer ignore-deprecated */ self::parseName($name);
Loading history...
450 20
            $class     = $param->getClass();
451
452 20
            if ($class) {
453 19
                $args[] = $this->getObjectParam($class->getName(), $vars);
454 18
            } elseif (1 == $type && !empty($vars)) {
455 10
                $args[] = array_shift($vars);
456 9
            } elseif (0 == $type && isset($vars[$name])) {
457 1
                $args[] = $vars[$name];
458 9
            } elseif (0 == $type && isset($vars[$lowerName])) {
459 1
                $args[] = $vars[$lowerName];
460 9
            } elseif ($param->isDefaultValueAvailable()) {
461 9
                $args[] = $param->getDefaultValue();
462
            } else {
463
                throw new InvalidArgumentException('method param miss:' . $name);
464
            }
465
        }
466
467 20
        return $args;
468
    }
469
470
    /**
471
     * 字符串命名风格转换
472
     * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
473
     * @deprecated
474
     * @access public
475
     * @param string  $name    字符串
476
     * @param integer $type    转换类型
477
     * @param bool    $ucfirst 首字母是否大写(驼峰规则)
478
     * @return string
479
     */
480 21
    public static function parseName(string $name = null, int $type = 0, bool $ucfirst = true): string
481
    {
482 21
        if ($type) {
483
            $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...
484 1
                return strtoupper($match[1]);
485 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...
486 1
            return $ucfirst ? ucfirst($name) : lcfirst($name);
487
        }
488
489 21
        return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
490
    }
491
492
    /**
493
     * 获取类名(不包含命名空间)
494
     * @deprecated
495
     * @access public
496
     * @param string|object $class
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
497
     * @return string
498
     */
499 1
    public static function classBaseName($class): string
500
    {
501 1
        $class = is_object($class) ? get_class($class) : $class;
502 1
        return basename(str_replace('\\', '/', $class));
503
    }
504
505
    /**
0 ignored issues
show
Coding Style introduced by
Parameter ...$args should have a doc-comment as per coding-style.
Loading history...
506
     * 创建工厂对象实例
507
     * @deprecated
508
     * @access public
509
     * @param string $name      工厂类名
510
     * @param string $namespace 默认命名空间
511
     * @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...
512
     * @return mixed
513
     */
514 4
    public static function factory(string $name, string $namespace = '', ...$args)
515
    {
516 4
        $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name);
517
518 4
        if (class_exists($class)) {
519 4
            return Container::getInstance()->invokeClass($class, $args);
520
        }
521
522 1
        throw new ClassNotFoundException('class not exists:' . $class, $class);
523
    }
524
525
    /**
526
     * 获取对象类型的参数值
527
     * @access protected
528
     * @param string $className 类名
529
     * @param array  $vars      参数
530
     * @return mixed
531
     */
532 19
    protected function getObjectParam(string $className, array &$vars)
533
    {
534 19
        $array = $vars;
535 19
        $value = array_shift($array);
536
537 19
        if ($value instanceof $className) {
538 1
            $result = $value;
539 1
            array_shift($vars);
540
        } else {
541 19
            $result = $this->make($className);
542
        }
543
544 19
        return $result;
545
    }
546
547 1
    public function __set($name, $value)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __set()
Loading history...
548
    {
549 1
        $this->bind($name, $value);
550 1
    }
551
552 22
    public function __get($name)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __get()
Loading history...
553
    {
554 22
        return $this->get($name);
555
    }
556
557 1
    public function __isset($name): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __isset()
Loading history...
558
    {
559 1
        return $this->exists($name);
560
    }
561
562 2
    public function __unset($name)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __unset()
Loading history...
563
    {
564 2
        $this->delete($name);
565 2
    }
566
567 1
    public function offsetExists($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetExists()
Loading history...
568
    {
569 1
        return $this->exists($key);
570
    }
571
572 1
    public function offsetGet($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetGet()
Loading history...
573
    {
574 1
        return $this->make($key);
575
    }
576
577 1
    public function offsetSet($key, $value)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetSet()
Loading history...
578
    {
579 1
        $this->bind($key, $value);
580 1
    }
581
582 1
    public function offsetUnset($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetUnset()
Loading history...
583
    {
584 1
        $this->delete($key);
585 1
    }
586
587
    //Countable
588
    public function count()
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
589
    {
590
        return count($this->instances);
591
    }
592
593
    //IteratorAggregate
594 1
    public function getIterator()
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
595
    {
596 1
        return new ArrayIterator($this->instances);
597
    }
598
}
599