Completed
Push — 6.0 ( fba450...3d0b31 )
by yun
04:36
created

Container::getIterator()   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 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 ReflectionMethod;
26
use think\exception\ClassNotFoundException;
27
28
/**
29
 * Class Container
30
 * @package think
0 ignored issues
show
Coding Style introduced by
Package name "think" is not valid; consider "Think" instead
Loading history...
31
 *
32
 * @property Route      $route
33
 * @property Config     $config
34
 * @property Cache      $cache
35
 * @property Request    $request
36
 * @property Http       $http
37
 * @property Console    $console
38
 * @property Env        $env
39
 * @property Event      $event
40
 * @property Middleware $middleware
41
 * @property Log        $log
42
 * @property Lang       $lang
43
 * @property Db         $db
44
 * @property Cookie     $cookie
45
 * @property Session    $session
46
 * @property Validate   $validate
47
 * @property Filesystem $filesystem
48
 */
49
class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable
50
{
51
    /**
52
     * 容器对象实例
53
     * @var Container|Closure
54
     */
55
    protected static $instance;
56
57
    /**
58
     * 容器中的对象实例
59
     * @var array
60
     */
61
    protected $instances = [];
62
63
    /**
64
     * 容器绑定标识
65
     * @var array
66
     */
67
    protected $bind = [
68
        'app'                     => App::class,
69
        'cache'                   => Cache::class,
70
        'config'                  => Config::class,
71
        'console'                 => Console::class,
72
        'cookie'                  => Cookie::class,
73
        'db'                      => Db::class,
74
        'env'                     => Env::class,
75
        'event'                   => Event::class,
76
        'http'                    => Http::class,
77
        'lang'                    => Lang::class,
78
        'log'                     => Log::class,
79
        'middleware'              => Middleware::class,
80
        'request'                 => Request::class,
81
        'response'                => Response::class,
82
        'route'                   => Route::class,
83
        'session'                 => Session::class,
84
        'validate'                => Validate::class,
85
        'view'                    => View::class,
86
        'filesystem'              => Filesystem::class,
87
88
        // 接口依赖注入
89
        'Psr\Log\LoggerInterface' => Log::class,
90
    ];
91
92
    /**
93
     * 容器回调
94
     *
95
     * @var array
96
     */
97
    protected $invokeCallback = [];
98
99
    /**
100
     * 获取当前容器的实例(单例)
101
     * @access public
102
     * @return static
103
     */
104 6
    public static function getInstance()
105
    {
106 6
        if (is_null(static::$instance)) {
107 1
            static::$instance = new static;
108
        }
109
110 6
        if (static::$instance instanceof Closure) {
111 1
            return (static::$instance)();
112
        }
113
114 6
        return static::$instance;
115
    }
116
117
    /**
118
     * 设置当前容器的实例
119
     * @access public
120
     * @param object|Closure $instance
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
121
     * @return void
122
     */
123 32
    public static function setInstance($instance): void
124
    {
125 32
        static::$instance = $instance;
126 32
    }
127
128
    /**
129
     * 注册一个容器对象回调
130
     *
131
     * @param  string|Closure $abstract
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
132
     * @param  Closure|null   $callback
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
133
     * @return void
134
     */
135
    public function resolving($abstract, Closure $callback = null): void
136
    {
137
        if ($abstract instanceof Closure) {
138
            $this->invokeCallback['*'][] = $abstract;
139
            return;
140
        }
141
142
        if (isset($this->bind[$abstract])) {
143
            $abstract = $this->bind[$abstract];
144
        }
145
146
        $this->invokeCallback[$abstract][] = $callback;
147
    }
148
149
    /**
150
     * 获取容器中的对象实例 不存在则创建
151
     * @access public
152
     * @param string     $abstract    类名或者标识
153
     * @param array|true $vars        变量
154
     * @param bool       $newInstance 是否每次创建新的实例
155
     * @return object
156
     */
157 1
    public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
158
    {
159 1
        return static::getInstance()->make($abstract, $vars, $newInstance);
160
    }
161
162
    /**
163
     * 获取容器中的对象实例
164
     * @access public
165
     * @param string $abstract 类名或者标识
166
     * @return object
167
     */
168 12
    public function get($abstract)
169
    {
170 12
        if ($this->has($abstract)) {
171 11
            return $this->make($abstract);
172
        }
173
174 1
        throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
175
    }
176
177
    /**
178
     * 绑定一个类、闭包、实例、接口实现到容器
179
     * @access public
180
     * @param string|array $abstract 类标识、接口
181
     * @param mixed        $concrete 要绑定的类、闭包或者实例
182
     * @return $this
183
     */
184 9
    public function bind($abstract, $concrete = null)
185
    {
186 9
        if (is_array($abstract)) {
187 4
            $this->bind = array_merge($this->bind, $abstract);
188 6
        } elseif ($concrete instanceof Closure) {
189 3
            $this->bind[$abstract] = $concrete;
190 4
        } elseif (is_object($concrete)) {
191 3
            $this->instance($abstract, $concrete);
192
        } else {
193 2
            $this->bind[$abstract] = $concrete;
194
        }
195
196 9
        return $this;
197
    }
198
199
    /**
200
     * 绑定一个类实例到容器
201
     * @access public
202
     * @param string $abstract 类名或者标识
203
     * @param object $instance 类的实例
204
     * @return $this
205
     */
206 22
    public function instance(string $abstract, $instance)
207
    {
208 22
        if (isset($this->bind[$abstract])) {
209 19
            $bind = $this->bind[$abstract];
210
211 19
            if (is_string($bind)) {
212 19
                return $this->instance($bind, $instance);
213
            }
214
        }
215
216 22
        $this->instances[$abstract] = $instance;
217
218 22
        return $this;
219
    }
220
221
    /**
222
     * 判断容器中是否存在类及标识
223
     * @access public
224
     * @param string $abstract 类名或者标识
225
     * @return bool
226
     */
227 12
    public function bound(string $abstract): bool
228
    {
229 12
        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
230
    }
231
232
    /**
233
     * 判断容器中是否存在类及标识
234
     * @access public
235
     * @param string $name 类名或者标识
236
     * @return bool
237
     */
238 12
    public function has($name): bool
239
    {
240 12
        return $this->bound($name);
241
    }
242
243
    /**
244
     * 判断容器中是否存在对象实例
245
     * @access public
246
     * @param string $abstract 类名或者标识
247
     * @return bool
248
     */
249 4
    public function exists(string $abstract): bool
250
    {
251 4
        if (isset($this->bind[$abstract])) {
252 3
            $bind = $this->bind[$abstract];
253
254 3
            if (is_string($bind)) {
255 2
                return $this->exists($bind);
256
            }
257
        }
258
259 4
        return isset($this->instances[$abstract]);
260
    }
261
262
    /**
263
     * 创建类的实例 已经存在则直接获取
264
     * @access public
265
     * @param string $abstract    类名或者标识
266
     * @param array  $vars        变量
267
     * @param bool   $newInstance 是否每次创建新的实例
268
     * @return mixed
269
     */
270 16
    public function make(string $abstract, array $vars = [], bool $newInstance = false)
271
    {
272 16
        if (isset($this->instances[$abstract]) && !$newInstance) {
273 13
            return $this->instances[$abstract];
274
        }
275
276 15
        if (isset($this->bind[$abstract])) {
277 13
            $concrete = $this->bind[$abstract];
278
279 13
            if ($concrete instanceof Closure) {
280 3
                $object = $this->invokeFunction($concrete, $vars);
281
            } else {
282 13
                return $this->make($concrete, $vars, $newInstance);
283
            }
284
        } else {
285 12
            $object = $this->invokeClass($abstract, $vars);
286
        }
287
288 15
        if (!$newInstance) {
289 15
            $this->instances[$abstract] = $object;
290
        }
291
292 15
        return $object;
293
    }
294
295
    /**
296
     * 删除容器中的对象实例
297
     * @access public
298
     * @param string $name 类名或者标识
299
     * @return void
300
     */
301 2
    public function delete($name)
302
    {
303 2
        if (isset($this->bind[$name])) {
304 2
            $bind = $this->bind[$name];
305
306 2
            if (is_string($bind)) {
307 2
                return $this->delete($bind);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->delete($bind) targeting think\Container::delete() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
308
            }
309
        }
310
311 2
        if (isset($this->instances[$name])) {
312 2
            unset($this->instances[$name]);
313
        }
314 2
    }
315
316
    /**
317
     * 执行函数或者闭包方法 支持参数调用
318
     * @access public
319
     * @param mixed $function 函数或者闭包
320
     * @param array $vars     参数
321
     * @return mixed
322
     */
323 5
    public function invokeFunction($function, array $vars = [])
324
    {
325
        try {
326 5
            $reflect = new ReflectionFunction($function);
327
328 4
            $args = $this->bindParams($reflect, $vars);
329
330 4
            return call_user_func_array($function, $args);
331 1
        } catch (ReflectionException $e) {
332 1
            throw new Exception('function not exists: ' . $function . '()');
333
        }
334
    }
335
336
    /**
337
     * 调用反射执行类的方法 支持参数绑定
338
     * @access public
339
     * @param mixed $method 方法
340
     * @param array $vars   参数
341
     * @return mixed
342
     */
343 3
    public function invokeMethod($method, array $vars = [])
344
    {
345
        try {
346 3
            if (is_array($method)) {
347 3
                $class   = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]);
348 3
                $reflect = new ReflectionMethod($class, $method[1]);
349
            } else {
350
                // 静态方法
351 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

351
                $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...
352
            }
353
354 2
            $args = $this->bindParams($reflect, $vars);
355
356 2
            return $reflect->invokeArgs($class ?? null, $args);
357 1
        } catch (ReflectionException $e) {
358 1
            if (is_array($method)) {
359 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...
360 1
                $callback = $class . '::' . $method[1];
361
            } else {
362
                $callback = $method;
363
            }
364
365 1
            throw new Exception('method not exists: ' . $callback . '()');
366
        }
367
    }
368
369
    /**
370
     * 调用反射执行类的方法 支持参数绑定
371
     * @access public
372
     * @param object $instance 对象实例
373
     * @param mixed  $reflect  反射类
374
     * @param array  $vars     参数
375
     * @return mixed
376
     */
377 1
    public function invokeReflectMethod($instance, $reflect, array $vars = [])
378
    {
379 1
        $args = $this->bindParams($reflect, $vars);
380
381 1
        return $reflect->invokeArgs($instance, $args);
382
    }
383
384
    /**
385
     * 调用反射执行callable 支持参数绑定
386
     * @access public
387
     * @param mixed $callable
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
388
     * @param array $vars 参数
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
389
     * @return mixed
390
     */
391 2
    public function invoke($callable, array $vars = [])
392
    {
393 2
        if ($callable instanceof Closure) {
394 1
            return $this->invokeFunction($callable, $vars);
395
        }
396
397 2
        return $this->invokeMethod($callable, $vars);
398
    }
399
400
    /**
401
     * 调用反射执行类的实例化 支持依赖注入
402
     * @access public
403
     * @param string $class 类名
404
     * @param array  $vars  参数
405
     * @return mixed
406
     */
407 18
    public function invokeClass(string $class, array $vars = [])
408
    {
409
        try {
410 18
            $reflect = new ReflectionClass($class);
411
412 17
            if ($reflect->hasMethod('__make')) {
413 11
                $method = new ReflectionMethod($class, '__make');
414
415 11
                if ($method->isPublic() && $method->isStatic()) {
416 11
                    $args = $this->bindParams($method, $vars);
417 11
                    return $method->invokeArgs(null, $args);
418
                }
419
            }
420
421 14
            $constructor = $reflect->getConstructor();
422
423 14
            $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...
424
425 14
            $object = $reflect->newInstanceArgs($args);
426
427 14
            $this->invokeAfter($class, $object);
428
429 14
            return $object;
430 1
        } catch (ReflectionException $e) {
431 1
            throw new ClassNotFoundException('class not exists: ' . $class, $class);
432
        }
433
    }
434
435
    /**
436
     * 执行invokeClass回调
437
     * @access protected
438
     * @param string $class  对象类名
439
     * @param object $object 容器对象实例
440
     * @return void
441
     */
442 14
    protected function invokeAfter(string $class, $object): void
443
    {
444 14
        if (isset($this->invokeCallback['*'])) {
445
            foreach ($this->invokeCallback['*'] as $callback) {
446
                $callback($object, $this);
447
            }
448
        }
449
450 14
        if (isset($this->invokeCallback[$class])) {
451
            foreach ($this->invokeCallback as $callback) {
452
                $callback($object, $this);
453
            }
454
        }
455 14
    }
456
457
    /**
458
     * 绑定参数
459
     * @access protected
460
     * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
461
     * @param array                                 $vars    参数
462
     * @return array
463
     */
464 20
    protected function bindParams($reflect, array $vars = []): array
465
    {
466 20
        if ($reflect->getNumberOfParameters() == 0) {
467 14
            return [];
468
        }
469
470
        // 判断数组类型 数字数组时按顺序绑定参数
471 14
        reset($vars);
472 14
        $type   = key($vars) === 0 ? 1 : 0;
473 14
        $params = $reflect->getParameters();
474 14
        $args   = [];
475
476 14
        foreach ($params as $param) {
477 14
            $name      = $param->getName();
478 14
            $lowerName = App::parseName($name);
479 14
            $class     = $param->getClass();
480
481 14
            if ($class) {
482 14
                $args[] = $this->getObjectParam($class->getName(), $vars);
483 12
            } elseif (1 == $type && !empty($vars)) {
484 4
                $args[] = array_shift($vars);
485 9
            } elseif (0 == $type && isset($vars[$name])) {
486 1
                $args[] = $vars[$name];
487 9
            } elseif (0 == $type && isset($vars[$lowerName])) {
488 1
                $args[] = $vars[$lowerName];
489 9
            } elseif ($param->isDefaultValueAvailable()) {
490 9
                $args[] = $param->getDefaultValue();
491
            } else {
492 14
                throw new InvalidArgumentException('method param miss:' . $name);
493
            }
494
        }
495
496 14
        return $args;
497
    }
498
499
    /**
500
     * 获取对象类型的参数值
501
     * @access protected
502
     * @param string $className 类名
503
     * @param array  $vars      参数
504
     * @return mixed
505
     */
506 14
    protected function getObjectParam(string $className, array &$vars)
507
    {
508 14
        $array = $vars;
509 14
        $value = array_shift($array);
510
511 14
        if ($value instanceof $className) {
512 1
            $result = $value;
513 1
            array_shift($vars);
514
        } else {
515 14
            $result = $this->make($className);
516
        }
517
518 14
        return $result;
519
    }
520
521 1
    public function __set($name, $value)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __set()
Loading history...
522
    {
523 1
        $this->bind($name, $value);
524 1
    }
525
526 14
    public function __get($name)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __get()
Loading history...
527
    {
528 14
        return $this->get($name);
529
    }
530
531 1
    public function __isset($name): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __isset()
Loading history...
532
    {
533 1
        return $this->exists($name);
534
    }
535
536 2
    public function __unset($name)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __unset()
Loading history...
537
    {
538 2
        $this->delete($name);
539 2
    }
540
541 1
    public function offsetExists($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetExists()
Loading history...
542
    {
543 1
        return $this->exists($key);
544
    }
545
546 1
    public function offsetGet($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetGet()
Loading history...
547
    {
548 1
        return $this->make($key);
549
    }
550
551 1
    public function offsetSet($key, $value)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetSet()
Loading history...
552
    {
553 1
        $this->bind($key, $value);
554 1
    }
555
556 1
    public function offsetUnset($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetUnset()
Loading history...
557
    {
558 1
        $this->delete($key);
559 1
    }
560
561
    //Countable
562
    public function count()
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
563
    {
564
        return count($this->instances);
565
    }
566
567
    //IteratorAggregate
568 1
    public function getIterator()
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
569
    {
570 1
        return new ArrayIterator($this->instances);
571
    }
572
}
573