Passed
Push — 8.0 ( 1ca27f...9a82e1 )
by liu
02:24
created

Container::bindParams()   C

Complexity

Conditions 15
Paths 17

Size

Total Lines 34
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 15.13

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 15
eloc 24
c 1
b 1
f 0
nc 17
nop 2
dl 0
loc 34
ccs 22
cts 24
cp 0.9167
crap 15.13
rs 5.9166

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~2023 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 ReflectionNamedType;
28
use ReflectionParameter;
29
use think\exception\ClassNotFoundException;
30
use think\exception\FuncNotFoundException;
31
use think\helper\Str;
32
use Traversable;
33
34
/**
35
 * 容器管理类 支持PSR-11
36
 */
37
class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable
38
{
39
    /**
40
     * 容器对象实例
41
     * @var Container|Closure
42
     */
43
    protected static $instance;
44
45
    /**
46
     * 容器中的对象实例
47
     * @var array
48
     */
49
    protected $instances = [];
50
51
    /**
52
     * 容器绑定标识
53
     * @var array
54
     */
55
    protected $bind = [];
56
57
    /**
58
     * 容器回调
59
     * @var array
60
     */
61
    protected $invokeCallback = [];
62
63
    /**
64
     * 获取当前容器的实例(单例)
65
     * @access public
66
     * @return static
67
     */
68 39
    public static function getInstance()
69
    {
70 39
        if (is_null(static::$instance)) {
71 6
            static::$instance = new static;
72
        }
73
74 39
        if (static::$instance instanceof Closure) {
75 3
            return (static::$instance)();
76
        }
77
78 39
        return static::$instance;
79
    }
80
81
    /**
82
     * 设置当前容器的实例
83
     * @access public
84
     * @param object|Closure $instance
85
     * @return void
86
     */
87 174
    public static function setInstance($instance): void
88
    {
89 174
        static::$instance = $instance;
90
    }
91
92
    /**
93
     * 注册一个容器对象回调
94
     *
95
     * @param string|Closure $abstract
96
     * @param Closure|null   $callback
97
     * @return void
98
     */
99 3
    public function resolving(string|Closure $abstract, ?Closure $callback = null): void
100
    {
101 3
        if ($abstract instanceof Closure) {
102 3
            $this->invokeCallback['*'][] = $abstract;
103 3
            return;
104
        }
105
106 3
        $abstract = $this->getAlias($abstract);
107
108 3
        $this->invokeCallback[$abstract][] = $callback;
109
    }
110
111
    /**
112
     * 获取容器中的对象实例 不存在则创建
113
     * @template T
114
     * @param string|class-string<T> $abstract    类名或者标识
115
     * @param array                  $vars        变量
116
     * @param bool                   $newInstance 是否每次创建新的实例
117
     * @return T|object
118
     */
119 3
    public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
120
    {
121 3
        return static::getInstance()->make($abstract, $vars, $newInstance);
122
    }
123
124
    /**
125
     * 获取容器中的对象实例
126
     * @template T
127
     * @param string|class-string<T> $abstract 类名或者标识
128
     * @return T|object
129
     */
130 57
    public function get(string $abstract)
131
    {
132 57
        if ($this->has($abstract)) {
133 54
            return $this->make($abstract);
134
        }
135
136 3
        throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
137
    }
138
139
    /**
140
     * 绑定一个类、闭包、实例、接口实现到容器
141
     * @access public
142
     * @param string|array $abstract 类标识、接口
143
     * @param mixed        $concrete 要绑定的类、闭包或者实例
144
     * @return $this
145
     */
146 27
    public function bind(string|array $abstract, $concrete = null)
147
    {
148 27
        if (is_array($abstract)) {
0 ignored issues
show
introduced by
The condition is_array($abstract) is always true.
Loading history...
149 9
            foreach ($abstract as $key => $val) {
150 6
                $this->bind($key, $val);
151
            }
152 24
        } elseif ($concrete instanceof Closure) {
153 9
            $this->bind[$abstract] = $concrete;
154 18
        } elseif (is_object($concrete)) {
155 12
            $this->instance($abstract, $concrete);
156
        } else {
157 9
            $abstract = $this->getAlias($abstract);
158 9
            if ($abstract != $concrete) {
159 9
                $this->bind[$abstract] = $concrete;
160
            }
161
        }
162
163 27
        return $this;
164
    }
165
166
    /**
167
     * 根据别名获取真实类名
168
     * @param string $abstract
169
     * @return string
170
     */
171 102
    public function getAlias(string $abstract): string
172
    {
173 102
        if (isset($this->bind[$abstract])) {
174 78
            $bind = $this->bind[$abstract];
175
176 78
            if (is_string($bind)) {
177 72
                return $this->getAlias($bind);
178
            }
179
        }
180
181 102
        return $abstract;
182
    }
183
184
    /**
185
     * 绑定一个类实例到容器
186
     * @access public
187
     * @param string $abstract 类名或者标识
188
     * @param object $instance 类的实例
189
     * @return $this
190
     */
191 45
    public function instance(string $abstract, $instance)
192
    {
193 45
        $abstract = $this->getAlias($abstract);
194
195 45
        $this->instances[$abstract] = $instance;
196
197 45
        return $this;
198
    }
199
200
    /**
201
     * 判断容器中是否存在类及标识
202
     * @access public
203
     * @param string $abstract 类名或者标识
204
     * @return bool
205
     */
206 57
    public function bound(string $abstract): bool
207
    {
208 57
        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
209
    }
210
211
    /**
212
     * 判断容器中是否存在类及标识
213
     * @access public
214
     * @param string $name 类名或者标识
215
     * @return bool
216
     */
217 57
    public function has(string $name): bool
218
    {
219 57
        return $this->bound($name);
220
    }
221
222
    /**
223
     * 判断容器中是否存在对象实例
224
     * @access public
225
     * @param string $abstract 类名或者标识
226
     * @return bool
227
     */
228 12
    public function exists(string $abstract): bool
229
    {
230 12
        $abstract = $this->getAlias($abstract);
231
232 12
        return isset($this->instances[$abstract]);
233
    }
234
235
    /**
236
     * 创建类的实例 已经存在则直接获取
237
     * @template T
238
     * @param string|class-string<T> $abstract    类名或者标识
239
     * @param array                  $vars        变量
240
     * @param bool                   $newInstance 是否每次创建新的实例
241
     * @return T|object
242
     */
243 81
    public function make(string $abstract, array $vars = [], bool $newInstance = false)
244
    {
245 81
        $abstract = $this->getAlias($abstract);
246
247 81
        if (isset($this->instances[$abstract]) && !$newInstance) {
248 45
            return $this->instances[$abstract];
249
        }
250
251 78
        if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
252 9
            $object = $this->invokeFunction($this->bind[$abstract], $vars);
253
        } else {
254 69
            $object = $this->invokeClass($abstract, $vars);
255
        }
256
257 78
        $this->invokeAfter($abstract, $object);
258
259 78
        if (!$newInstance) {
260 78
            $this->instances[$abstract] = $object;
261
        }
262
263 78
        return $object;
264
    }
265
266
    /**
267
     * 删除容器中的对象实例
268
     * @access public
269
     * @param string $name 类名或者标识
270
     * @return void
271
     */
272 6
    public function delete(string $name)
273
    {
274 6
        $name = $this->getAlias($name);
275
276 6
        if (isset($this->instances[$name])) {
277 6
            unset($this->instances[$name]);
278
        }
279
    }
280
281
    /**
282
     * 执行函数或者闭包方法 支持参数调用
283
     * @access public
284
     * @param string|Closure $function 函数或者闭包
285
     * @param array          $vars     参数
286
     * @return mixed
287
     */
288 36
    public function invokeFunction(string|Closure $function, array $vars = [])
289
    {
290
        try {
291 36
            $reflect = new ReflectionFunction($function);
292 3
        } catch (ReflectionException $e) {
293 3
            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

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