Completed
Pull Request — 6.0 (#2115)
by nhzex
06:13
created

Container::get()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 10
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 InvalidArgumentException;
20
use IteratorAggregate;
21
use Psr\Container\ContainerInterface;
22
use ReflectionClass;
23
use ReflectionException;
24
use ReflectionFunction;
25
use ReflectionFunctionAbstract;
26
use ReflectionMethod;
27
use think\exception\ClassNotFoundException;
28
use think\exception\FuncNotFoundException;
29
use think\helper\Str;
30
31
/**
32
 * 容器管理类 支持PSR-11
33
 */
34
class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable
35
{
36
    /**
37
     * 容器对象实例
38
     * @var Container|Closure
39
     */
40
    protected static $instance;
41
42
    /**
43
     * 容器中的对象实例
44
     * @var array
45
     */
46
    protected $instances = [];
47
48
    /**
49
     * 容器绑定标识
50
     * @var array
51
     */
52
    protected $bind = [];
53
54
    /**
55
     * 容器回调
56
     * @var array
57
     */
58
    protected $invokeCallback = [];
59
60
    /**
61
     * 获取当前容器的实例(单例)
62
     * @access public
63
     * @return static
64
     */
65 3
    public static function getInstance()
66
    {
67 3
        if (is_null(static::$instance)) {
68 1
            static::$instance = new static;
69
        }
70
71 3
        if (static::$instance instanceof Closure) {
72 1
            return (static::$instance)();
73
        }
74
75 3
        return static::$instance;
76
    }
77
78
    /**
79
     * 设置当前容器的实例
80
     * @access public
81
     * @param object|Closure $instance
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
82
     * @return void
83
     */
84 53
    public static function setInstance($instance): void
85
    {
86 53
        static::$instance = $instance;
87 53
    }
88
89
    /**
90
     * 注册一个容器对象回调
91
     *
92
     * @param string|Closure $abstract
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
93
     * @param Closure|null   $callback
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
94
     * @return void
95
     */
96 1
    public function resolving($abstract, Closure $callback = null): void
97
    {
98 1
        if ($abstract instanceof Closure) {
99 1
            $this->invokeCallback['*'][] = $abstract;
100 1
            return;
101
        }
102
103 1
        $abstract = $this->getAlias($abstract);
104
105 1
        $this->invokeCallback[$abstract][] = $callback;
106 1
    }
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 9
    public function get($abstract)
128
    {
129 9
        if ($this->has($abstract)) {
130 8
            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 3
            foreach ($abstract as $key => $val) {
147 2
                $this->bind($key, $val);
148
            }
149 8
        } elseif ($concrete instanceof Closure) {
150 3
            $this->bind[$abstract] = $concrete;
151 6
        } elseif (is_object($concrete)) {
152 4
            $this->instance($abstract, $concrete);
153
        } else {
154 3
            $abstract = $this->getAlias($abstract);
155
156 3
            $this->bind[$abstract] = $concrete;
157
        }
158
159 9
        return $this;
160
    }
161
162
    /**
163
     * 根据别名获取真实类名
164
     * @param  string $abstract
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
165
     * @return string
166
     */
167 28
    public function getAlias(string $abstract): string
168
    {
169 28
        if (isset($this->bind[$abstract])) {
170 17
            $bind = $this->bind[$abstract];
171
172 17
            if (is_string($bind)) {
173 15
                return $this->getAlias($bind);
174
            }
175
        }
176
177 28
        return $abstract;
178
    }
179
180
    /**
181
     * 绑定一个类实例到容器
182
     * @access public
183
     * @param string $abstract 类名或者标识
184
     * @param object $instance 类的实例
185
     * @return $this
186
     */
187 15
    public function instance(string $abstract, $instance)
188
    {
189 15
        $abstract = $this->getAlias($abstract);
190
191 15
        $this->instances[$abstract] = $instance;
192
193 15
        return $this;
194
    }
195
196
    /**
197
     * 判断容器中是否存在类及标识
198
     * @access public
199
     * @param string $abstract 类名或者标识
200
     * @return bool
201
     */
202 9
    public function bound(string $abstract): bool
203
    {
204 9
        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
205
    }
206
207
    /**
208
     * 判断容器中是否存在类及标识
209
     * @access public
210
     * @param string $name 类名或者标识
211
     * @return bool
212
     */
213 9
    public function has($name): bool
214
    {
215 9
        return $this->bound($name);
216
    }
217
218
    /**
219
     * 判断容器中是否存在对象实例
220
     * @access public
221
     * @param string $abstract 类名或者标识
222
     * @return bool
223
     */
224 4
    public function exists(string $abstract): bool
225
    {
226 4
        $abstract = $this->getAlias($abstract);
227
228 4
        return isset($this->instances[$abstract]);
229
    }
230
231
    /**
232
     * 创建类的实例 已经存在则直接获取
233
     * @access public
234
     * @param string $abstract    类名或者标识
235
     * @param array  $vars        变量
236
     * @param bool   $newInstance 是否每次创建新的实例
237
     * @return mixed
238
     */
239 20
    public function make(string $abstract, array $vars = [], bool $newInstance = false)
240
    {
241 20
        $abstract = $this->getAlias($abstract);
242
243 20
        if (isset($this->instances[$abstract]) && !$newInstance) {
244 9
            return $this->instances[$abstract];
245
        }
246
247 18
        if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
248 3
            $object = $this->invokeFunction($this->bind[$abstract], $vars);
249
        } else {
250 15
            $object = $this->invokeClass($abstract, $vars);
251
        }
252
253 18
        if (!$newInstance) {
254 18
            $this->instances[$abstract] = $object;
255
        }
256
257 18
        return $object;
258
    }
259
260
    /**
261
     * 删除容器中的对象实例
262
     * @access public
263
     * @param string $name 类名或者标识
264
     * @return void
265
     */
266 2
    public function delete($name)
267
    {
268 2
        $name = $this->getAlias($name);
269
270 2
        if (isset($this->instances[$name])) {
271 2
            unset($this->instances[$name]);
272
        }
273 2
    }
274
275
    /**
276
     * 执行函数或者闭包方法 支持参数调用
277
     * @access public
278
     * @param string|Closure $function 函数或者闭包
279
     * @param array          $vars     参数
280
     * @return mixed
281
     */
282 7
    public function invokeFunction($function, array $vars = [])
283
    {
284
        try {
285 7
            $reflect = new ReflectionFunction($function);
286 1
        } catch (ReflectionException $e) {
287 1
            throw new FuncNotFoundException("function not exists: {$function}()", $function, $e);
0 ignored issues
show
Bug introduced by
It seems like $function can also be of type Closure; however, parameter $func of think\exception\FuncNotF...xception::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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