Completed
Push — 6.0 ( 58c1e9...192674 )
by liu
02:29
created

Container::offsetUnset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
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 42
    public static function getInstance()
66
    {
67 42
        if (is_null(static::$instance)) {
68 3
            static::$instance = new static;
69
        }
70
71 42
        if (static::$instance instanceof Closure) {
72 3
            return (static::$instance)();
73
        }
74
75 42
        return static::$instance;
76
    }
77
78
    /**
79
     * 设置当前容器的实例
80
     * @access public
81
     * @param object|Closure $instance
82
     * @return void
83
     */
84 189
    public static function setInstance($instance): void
85
    {
86 189
        static::$instance = $instance;
87 189
    }
88
89
    /**
90
     * 注册一个容器对象回调
91
     *
92
     * @param string|Closure $abstract
93
     * @param Closure|null   $callback
94
     * @return void
95
     */
96 3
    public function resolving($abstract, Closure $callback = null): void
97
    {
98 3
        if ($abstract instanceof Closure) {
99 3
            $this->invokeCallback['*'][] = $abstract;
100 3
            return;
101
        }
102
103 3
        $abstract = $this->getAlias($abstract);
104
105 3
        $this->invokeCallback[$abstract][] = $callback;
106 3
    }
107
108
    /**
109
     * 获取容器中的对象实例 不存在则创建
110
     * @access public
111
     * @param string     $abstract    类名或者标识
112
     * @param array|true $vars        变量
113
     * @param bool       $newInstance 是否每次创建新的实例
114
     * @return object
115
     */
116 3
    public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
117
    {
118 3
        return static::getInstance()->make($abstract, $vars, $newInstance);
119
    }
120
121
    /**
122
     * 获取容器中的对象实例
123
     * @access public
124
     * @param string $abstract 类名或者标识
125
     * @return object
126
     */
127 60
    public function get($abstract)
128
    {
129 60
        if ($this->has($abstract)) {
130 57
            return $this->make($abstract);
131
        }
132
133 3
        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 27
    public function bind($abstract, $concrete = null)
144
    {
145 27
        if (is_array($abstract)) {
146 9
            foreach ($abstract as $key => $val) {
147 7
                $this->bind($key, $val);
148
            }
149 24
        } elseif ($concrete instanceof Closure) {
150 9
            $this->bind[$abstract] = $concrete;
151 18
        } elseif (is_object($concrete)) {
152 12
            $this->instance($abstract, $concrete);
153
        } else {
154 9
            $abstract = $this->getAlias($abstract);
155 9
            if ($abstract != $concrete) {
156 9
                $this->bind[$abstract] = $concrete;
157
            }
158
        }
159
160 27
        return $this;
161
    }
162
163
    /**
164
     * 根据别名获取真实类名
165
     * @param  string $abstract
166
     * @return string
167
     */
168 117
    public function getAlias(string $abstract): string
169
    {
170 117
        if (isset($this->bind[$abstract])) {
171 84
            $bind = $this->bind[$abstract];
172
173 84
            if (is_string($bind)) {
174 78
                return $this->getAlias($bind);
175
            }
176
        }
177
178 117
        return $abstract;
179
    }
180
181
    /**
182
     * 绑定一个类实例到容器
183
     * @access public
184
     * @param string $abstract 类名或者标识
185
     * @param object $instance 类的实例
186
     * @return $this
187
     */
188 45
    public function instance(string $abstract, $instance)
189
    {
190 45
        $abstract = $this->getAlias($abstract);
191
192 45
        $this->instances[$abstract] = $instance;
193
194 45
        return $this;
195
    }
196
197
    /**
198
     * 判断容器中是否存在类及标识
199
     * @access public
200
     * @param string $abstract 类名或者标识
201
     * @return bool
202
     */
203 60
    public function bound(string $abstract): bool
204
    {
205 60
        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
206
    }
207
208
    /**
209
     * 判断容器中是否存在类及标识
210
     * @access public
211
     * @param string $name 类名或者标识
212
     * @return bool
213
     */
214 60
    public function has($name): bool
215
    {
216 60
        return $this->bound($name);
217
    }
218
219
    /**
220
     * 判断容器中是否存在对象实例
221
     * @access public
222
     * @param string $abstract 类名或者标识
223
     * @return bool
224
     */
225 12
    public function exists(string $abstract): bool
226
    {
227 12
        $abstract = $this->getAlias($abstract);
228
229 12
        return isset($this->instances[$abstract]);
230
    }
231
232
    /**
233
     * 创建类的实例 已经存在则直接获取
234
     * @access public
235
     * @param string $abstract    类名或者标识
236
     * @param array  $vars        变量
237
     * @param bool   $newInstance 是否每次创建新的实例
238
     * @return mixed
239
     */
240 93
    public function make(string $abstract, array $vars = [], bool $newInstance = false)
241
    {
242 93
        $abstract = $this->getAlias($abstract);
243
244 93
        if (isset($this->instances[$abstract]) && !$newInstance) {
245 51
            return $this->instances[$abstract];
246
        }
247
248 87
        if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
249 9
            $object = $this->invokeFunction($this->bind[$abstract], $vars);
250
        } else {
251 78
            $object = $this->invokeClass($abstract, $vars);
252
        }
253
254 87
        if (!$newInstance) {
255 87
            $this->instances[$abstract] = $object;
256
        }
257
258 87
        return $object;
259
    }
260
261
    /**
262
     * 删除容器中的对象实例
263
     * @access public
264
     * @param string $name 类名或者标识
265
     * @return void
266
     */
267 6
    public function delete($name)
268
    {
269 6
        $name = $this->getAlias($name);
270
271 6
        if (isset($this->instances[$name])) {
272 6
            unset($this->instances[$name]);
273
        }
274 6
    }
275
276
    /**
277
     * 执行函数或者闭包方法 支持参数调用
278
     * @access public
279
     * @param string|Closure $function 函数或者闭包
280
     * @param array          $vars     参数
281
     * @return mixed
282
     */
283 42
    public function invokeFunction($function, array $vars = [])
284
    {
285
        try {
286 42
            $reflect = new ReflectionFunction($function);
287 3
        } catch (ReflectionException $e) {
288 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

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