Completed
Push — 6.0 ( cba287...a38ebc )
by liu
06:03
created

Container::invokeClass()   A

Complexity

Conditions 6
Paths 23

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 6
eloc 14
c 2
b 0
f 0
nc 23
nop 2
dl 0
loc 25
ccs 14
cts 14
cp 1
crap 6
rs 9.2222
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