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

Container::invokeMethod()   B

Complexity

Conditions 7
Paths 12

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 12
nop 3
dl 0
loc 25
ccs 13
cts 13
cp 1
crap 7
rs 8.8333
c 0
b 0
f 0
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