Passed
Pull Request — 6.0 (#1750)
by guanguans
03:44
created

Container::invokeClass()   A

Complexity

Conditions 6
Paths 19

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 12
nc 19
nop 2
dl 0
loc 21
ccs 12
cts 12
cp 1
crap 6
rs 9.2222
c 0
b 0
f 0
1
<?php
2
// +----------------------------------------------------------------------
1 ignored issue
show
Coding Style introduced by
You must use "/**" style comments for a file comment
Loading history...
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 Debug      $debug
40
 * @property Event      $event
41
 * @property Middleware $middleware
42
 * @property Log        $log
43
 * @property Lang       $lang
44
 * @property Db         $db
45
 * @property Cookie     $cookie
46
 * @property Session    $session
47
 * @property Validate   $validate
48
 */
4 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
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
        'debug'                   => Debug::class,
75
        'env'                     => Env::class,
76
        'event'                   => Event::class,
77
        'http'                    => Http::class,
78
        'lang'                    => Lang::class,
79
        'log'                     => Log::class,
80
        'middleware'              => Middleware::class,
81
        'request'                 => Request::class,
82
        'response'                => Response::class,
83
        'route'                   => Route::class,
84
        'session'                 => Session::class,
85
        'validate'                => Validate::class,
86
        'view'                    => View::class,
87
88
        // 接口依赖注入
89
        'Psr\Log\LoggerInterface' => Log::class,
90
    ];
91
92
    /**
93
     * 获取当前容器的实例(单例)
94
     * @access public
95
     * @return static
96
     */
97 4
    public static function getInstance()
98
    {
99 4
        if (static::$instance === null) {
100 2
            static::$instance = new static;
101
        }
102
103 4
        if (static::$instance instanceof Closure) {
104 1
            return (static::$instance)();
105
        }
106
107 4
        return static::$instance;
108
    }
109
110
    /**
111
     * 设置当前容器的实例
112
     * @access public
113
     * @param object|Closure $instance
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
114
     * @return void
115
     */
116 29
    public static function setInstance($instance): void
117
    {
118 29
        static::$instance = $instance;
119 29
    }
120
121
    /**
122
     * 获取容器中的对象实例 不存在则创建
123
     * @access public
124
     * @param string     $abstract    类名或者标识
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
125
     * @param array|true $vars        变量
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
126
     * @param bool       $newInstance 是否每次创建新的实例
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
127
     * @return object
128
     */
129 1
    public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
130
    {
131 1
        return static::getInstance()->make($abstract, $vars, $newInstance);
132
    }
133
134
    /**
135
     * 获取容器中的对象实例
136
     * @access public
137
     * @param string $abstract 类名或者标识
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
138
     * @return object
139
     */
140 12
    public function get($abstract)
141
    {
142 12
        if ($this->has($abstract)) {
143 11
            return $this->make($abstract);
144
        }
145
146 1
        throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
147
    }
148
149
    /**
150
     * 绑定一个类、闭包、实例、接口实现到容器
151
     * @access public
152
     * @param string|array $abstract 类标识、接口
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
153
     * @param mixed        $concrete 要绑定的类、闭包或者实例
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
154
     * @return $this
155
     */
156 9
    public function bind($abstract, $concrete = null)
157
    {
158 9
        if (is_array($abstract)) {
159 4
            $this->bind = array_merge($this->bind, $abstract);
160 6
        } elseif ($concrete instanceof Closure) {
161 3
            $this->bind[$abstract] = $concrete;
162 4
        } elseif (is_object($concrete)) {
163 3
            $this->instance($abstract, $concrete);
164
        } else {
165 2
            $this->bind[$abstract] = $concrete;
166
        }
167
168 9
        return $this;
169
    }
170
171
    /**
172
     * 绑定一个类实例到容器
173
     * @access public
174
     * @param string $abstract 类名或者标识
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
175
     * @param object $instance 类的实例
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
176
     * @return $this
177
     */
178 22
    public function instance(string $abstract, $instance)
179
    {
180 22
        if (isset($this->bind[$abstract])) {
181 19
            $bind = $this->bind[$abstract];
182
183 19
            if (is_string($bind)) {
184 19
                return $this->instance($bind, $instance);
185
            }
186
        }
187
188 22
        $this->instances[$abstract] = $instance;
189
190 22
        return $this;
191
    }
192
193
    /**
194
     * 判断容器中是否存在类及标识
195
     * @access public
196
     * @param string $abstract 类名或者标识
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
197
     * @return bool
198
     */
199 12
    public function bound(string $abstract): bool
200
    {
201 12
        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
202
    }
203
204
    /**
205
     * 判断容器中是否存在类及标识
206
     * @access public
207
     * @param string $name 类名或者标识
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
208
     * @return bool
209
     */
210 12
    public function has($name): bool
211
    {
212 12
        return $this->bound($name);
213
    }
214
215
    /**
216
     * 判断容器中是否存在对象实例
217
     * @access public
218
     * @param string $abstract 类名或者标识
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
219
     * @return bool
220
     */
221 4
    public function exists(string $abstract): bool
222
    {
223 4
        if (isset($this->bind[$abstract])) {
224 3
            $bind = $this->bind[$abstract];
225
226 3
            if (is_string($bind)) {
227 2
                return $this->exists($bind);
228
            }
229
        }
230
231 4
        return isset($this->instances[$abstract]);
232
    }
233
234
    /**
235
     * 创建类的实例 已经存在则直接获取
236
     * @access public
237
     * @param string $abstract    类名或者标识
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
238
     * @param array  $vars        变量
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
239
     * @param bool   $newInstance 是否每次创建新的实例
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
240
     * @return mixed
241
     */
242 16
    public function make(string $abstract, array $vars = [], bool $newInstance = false)
243
    {
244 16
        if (isset($this->instances[$abstract]) && !$newInstance) {
245 12
            return $this->instances[$abstract];
246
        }
247
248 15
        if (isset($this->bind[$abstract])) {
249 13
            $concrete = $this->bind[$abstract];
250
251 13
            if ($concrete instanceof Closure) {
252 3
                $object = $this->invokeFunction($concrete, $vars);
253
            } else {
254 13
                return $this->make($concrete, $vars, $newInstance);
255
            }
256
        } else {
257 12
            $object = $this->invokeClass($abstract, $vars);
258
        }
259
260 15
        if (!$newInstance) {
261 15
            $this->instances[$abstract] = $object;
262
        }
263
264 15
        return $object;
265
    }
266
267
    /**
268
     * 删除容器中的对象实例
269
     * @access public
270
     * @param string $name 类名或者标识
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
271
     * @return void
272
     */
273 2
    public function delete($name)
274
    {
275 2
        if (isset($this->bind[$name])) {
276 2
            $bind = $this->bind[$name];
277
278 2
            if (is_string($bind)) {
279 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...
280
            }
281
        }
282
283 2
        if (isset($this->instances[$name])) {
284 2
            unset($this->instances[$name]);
285
        }
286 2
    }
287
288
    /**
289
     * 执行函数或者闭包方法 支持参数调用
290
     * @access public
291
     * @param mixed $function 函数或者闭包
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
292
     * @param array $vars     参数
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
293
     * @return mixed
294
     */
295 5
    public function invokeFunction($function, array $vars = [])
296
    {
297
        try {
298 5
            $reflect = new ReflectionFunction($function);
299
300 4
            $args = $this->bindParams($reflect, $vars);
301
302 4
            return call_user_func_array($function, $args);
303 1
        } catch (ReflectionException $e) {
304 1
            throw new Exception('function not exists: ' . $function . '()');
305
        }
306
    }
307
308
    /**
309
     * 调用反射执行类的方法 支持参数绑定
310
     * @access public
311
     * @param mixed $method 方法
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
312
     * @param array $vars   参数
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
313
     * @return mixed
314
     */
315 3
    public function invokeMethod($method, array $vars = [])
316
    {
317
        try {
318 3
            if (is_array($method)) {
319 3
                $class   = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]);
320 3
                $reflect = new ReflectionMethod($class, $method[1]);
321
            } else {
322
                // 静态方法
323 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

323
                $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...
324
            }
325
326 2
            $args = $this->bindParams($reflect, $vars);
327
328 2
            return $reflect->invokeArgs($class ?? null, $args);
329 1
        } catch (ReflectionException $e) {
330 1
            if (is_array($method)) {
331 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...
332 1
                $callback = $class . '::' . $method[1];
333
            } else {
334
                $callback = $method;
335
            }
336
337 1
            throw new Exception('method not exists: ' . $callback . '()');
338
        }
339
    }
340
341
    /**
342
     * 调用反射执行类的方法 支持参数绑定
343
     * @access public
344
     * @param object $instance 对象实例
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
345
     * @param mixed  $reflect  反射类
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
346
     * @param array  $vars     参数
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
347
     * @return mixed
348
     */
349 1
    public function invokeReflectMethod($instance, $reflect, array $vars = [])
350
    {
351 1
        $args = $this->bindParams($reflect, $vars);
352
353 1
        return $reflect->invokeArgs($instance, $args);
354
    }
355
356
    /**
357
     * 调用反射执行callable 支持参数绑定
358
     * @access public
359
     * @param mixed $callable
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
360
     * @param array $vars 参数
1 ignored issue
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
361
     * @return mixed
362
     */
363 2
    public function invoke($callable, array $vars = [])
364
    {
365 2
        if ($callable instanceof Closure) {
366 1
            return $this->invokeFunction($callable, $vars);
367
        }
368
369 2
        return $this->invokeMethod($callable, $vars);
370
    }
371
372
    /**
373
     * 调用反射执行类的实例化 支持依赖注入
374
     * @access public
375
     * @param string $class 类名
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
376
     * @param array  $vars  参数
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
377
     * @return mixed
378
     */
379 16
    public function invokeClass(string $class, array $vars = [])
380
    {
381
        try {
382 16
            $reflect = new ReflectionClass($class);
383
384 15
            if ($reflect->hasMethod('__make')) {
385 11
                $method = new ReflectionMethod($class, '__make');
386
387 11
                if ($method->isPublic() && $method->isStatic()) {
388 11
                    $args = $this->bindParams($method, $vars);
389 11
                    return $method->invokeArgs(null, $args);
390
                }
391
            }
392
393 12
            $constructor = $reflect->getConstructor();
394
395 12
            $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...
396
397 12
            return $reflect->newInstanceArgs($args);
398 1
        } catch (ReflectionException $e) {
399 1
            throw new ClassNotFoundException('class not exists: ' . $class, $class);
400
        }
401
    }
402
403
    /**
404
     * 绑定参数
405
     * @access protected
406
     * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
407
     * @param array                                 $vars    参数
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
408
     * @return array
409
     */
410 18
    protected function bindParams($reflect, array $vars = []): array
411
    {
412 18
        if ($reflect->getNumberOfParameters() == 0) {
413 7
            return [];
414
        }
415
416
        // 判断数组类型 数字数组时按顺序绑定参数
417 11
        reset($vars);
418 11
        $type   = key($vars) === 0 ? 1 : 0;
419 11
        $params = $reflect->getParameters();
420 11
        $args   = [];
421
422 11
        foreach ($params as $param) {
423 11
            $name      = $param->getName();
424 11
            $lowerName = App::parseName($name);
425 11
            $class     = $param->getClass();
426
427 11
            if ($class) {
428 11
                $args[] = $this->getObjectParam($class->getName(), $vars);
429 9
            } elseif (1 == $type && !empty($vars)) {
430 1
                $args[] = array_shift($vars);
431 9
            } elseif (0 == $type && isset($vars[$name])) {
432 1
                $args[] = $vars[$name];
433 9
            } elseif (0 == $type && isset($vars[$lowerName])) {
434 1
                $args[] = $vars[$lowerName];
435 9
            } elseif ($param->isDefaultValueAvailable()) {
436 9
                $args[] = $param->getDefaultValue();
437
            } else {
438 11
                throw new InvalidArgumentException('method param miss:' . $name);
439
            }
440
        }
441
442 11
        return $args;
443
    }
444
445
    /**
446
     * 获取对象类型的参数值
447
     * @access protected
448
     * @param string $className 类名
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
449
     * @param array  $vars      参数
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
450
     * @return mixed
451
     */
452 11
    protected function getObjectParam(string $className, array &$vars)
453
    {
454 11
        $array = $vars;
455 11
        $value = array_shift($array);
456
457 11
        if ($value instanceof $className) {
458 1
            $result = $value;
459 1
            array_shift($vars);
460
        } else {
461 11
            $result = $this->make($className);
462
        }
463
464 11
        return $result;
465
    }
466
467 1
    public function __set($name, $value)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __set()
Loading history...
468
    {
469 1
        $this->bind($name, $value);
470 1
    }
471
472 11
    public function __get($name)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __get()
Loading history...
473
    {
474 11
        return $this->get($name);
475
    }
476
477 1
    public function __isset($name): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __isset()
Loading history...
478
    {
479 1
        return $this->exists($name);
480
    }
481
482 2
    public function __unset($name)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __unset()
Loading history...
483
    {
484 2
        $this->delete($name);
485 2
    }
486
487 1
    public function offsetExists($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetExists()
Loading history...
488
    {
489 1
        return $this->exists($key);
490
    }
491
492 1
    public function offsetGet($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetGet()
Loading history...
493
    {
494 1
        return $this->make($key);
495
    }
496
497 1
    public function offsetSet($key, $value)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetSet()
Loading history...
498
    {
499 1
        $this->bind($key, $value);
500 1
    }
501
502 1
    public function offsetUnset($key)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function offsetUnset()
Loading history...
503
    {
504 1
        $this->delete($key);
505 1
    }
506
507
    //Countable
508
    public function count()
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
509
    {
510
        return count($this->instances);
511
    }
512
513
    //IteratorAggregate
514 1
    public function getIterator()
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
515
    {
516 1
        return new ArrayIterator($this->instances);
517
    }
518
}
519