Passed
Branch master (3728bb)
by Taosikai
24:09 queued 09:25
created

Container.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * slince dependency injection component
4
 * @author Tao <[email protected]>
5
 */
6
namespace Slince\Di;
7
8
use Slince\Di\Exception\ConfigException;
9
use Slince\Di\Exception\DependencyInjectionException;
10
11
class Container
12
{
13
    /**
14
     * 所有需要分享的类及其实例
15
     * @var array
16
     */
17
    protected $shares = [];
18
19
    /**
20
     * 预定义的依赖,支持instance、callable、Definition、class
21
     * @var array
22
     */
23
    protected $definitions = [];
24
25
    /**
26
     * 全局接口与类绑定关系
27
     * @var array
28
     */
29
    protected $contextBindings = [];
30
31
    /**
32
     * 参数集合
33
     * @var ParameterStore
34
     */
35
    protected $parameterStore;
36
37
    public function __construct()
38
    {
39
        //全局参数存储
40
        $this->parameterStore = new ParameterStore();
41
    }
42
43
    /**
44
     * 给指定类或者别名指向类设置实例化向导
45
     * @param string $name
46
     * @param string $class 类名
47
     * @param array $arguments 构造函数
48
     * @param array $methodCalls setter注入
49
     * @param array $properties 属性注入
50
     * @return Definition
51
     */
52
    public function define($name, $class, array $arguments, array $methodCalls = [], array $properties = [])
53
    {
54
        $definition = new Definition($class, $arguments, $methodCalls, $properties);
55
        $this->setDefinition($name, $definition);
0 ignored issues
show
Deprecated Code introduced by
The method Slince\Di\Container::setDefinition() has been deprecated with message: Will be protected, Use define & share or set instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
56
        return $definition;
57
    }
58
59
    /**
60
     * 设置指定类的实例化代理
61
     * @param string $name
62
     * @param mixed $creation 闭包及其它合法可调用的语法结构
63
     * @throws ConfigException
64
     * @return $this
65
     */
66
    public function delegate($name, $creation)
67
    {
68
        if (!is_callable($creation)) {
69
            throw new ConfigException(sprintf("Delegate expects a valid callable or executable class::method string at Argument 2"));
70
        }
71
        $this->definitions[$name] = $creation;
72
        return $this;
73
    }
74
75
    /**
76
     * 绑定实例
77
     * ```
78
     * $container->instance('user', $user);
79
     * //或者直接提供实例
80
     * $container->instance($user);
81
     *
82
     * ```
83
     * @param $name
84
     * @param $instance
85
     * @throws ConfigException
86
     * @return $this
87
     */
88
    public function instance($name, $instance)
89
    {
90
        if (func_get_args() == 1) {
91
            if (!is_object($name)) {
92
                throw new ConfigException(sprintf("Instance expects a valid object"));
93
            }
94
            $instance = $name;
95
            $name = get_class($instance);
96
        }
97
        $this->definitions[$name] = $instance;
98
        $this->share($name);
99
        return $this;
100
    }
101
102
    /**
103
     * 将name直接绑定到某个指定存在的类(可用来绑定接口或者抽象类与实现类)
104
     * @param string $name
105
     * @param string $class 一个可被实例化的类名
106
     * @param string|array $context 为指定的上下文设置绑定指令
107
     * @throws ConfigException
108
     * @return $this
109
     */
110
    public function bind($name, $class, $context = null)
111
    {
112
        if (is_null($context)) {
113
            $this->definitions[$name] = $class;
114
        } else {
115
            if (is_array($context)) {
116
                list($contextClass, $contextMethod) = $context;
117
            } else {
118
                $contextClass = $context;
119
                $contextMethod = 'general';
120
            }
121
            isset($this->contextBindings[$contextClass][$contextMethod])
122
                || ($this->contextBindings[$contextClass][$contextMethod] = []);
123
            $this->contextBindings[$contextClass][$contextMethod][$name] = $class;
124
        }
125
        return $this;
126
    }
127
128
    /**
129
     * 给指定类或者类别名设置实例化指令
130
     * ```
131
     * //直接绑定实例
132
     * $container->set('student', $student);
133
     *
134
     * //绑定闭包或者其它可调用结构
135
     * $container->set('student', 'StudentFactory::create');
136
     * $container->set('student', function(){
137
     *     return new Student();
138
     * });
139
     *
140
     * //绑定预定义
141
     * $container->set('student', new Definition('Foo\Bar\StudentClass', [
142
     *      'gender' => 'boy',
143
     *      'school' => new Reference('school')
144
     * ], [
145
     *     'setAge' => [18]
146
     * ], [
147
     *     'father' => 'James',
148
     *     'mather' => 'Sophie'
149
     * ]));
150
     *
151
     * //绑定到指定类
152
     * $container->set('student', Foo\Bar\StudentClass);
153
     * ```
154
     * @param string $name
155
     * @param mixed $definition
156
     * @param boolean $share
157
     * @throws ConfigException
158
     * @return $this
159
     */
160
    public function set($name, $definition, $share = false)
161
    {
162
        if (is_callable($definition)) {
163
            $this->delegate($name, $definition);
164
            $share && $this->share($name);
165
        } elseif ($definition instanceof Definition) {
166
            $this->setDefinition($name, $definition, $share);
0 ignored issues
show
Deprecated Code introduced by
The method Slince\Di\Container::setDefinition() has been deprecated with message: Will be protected, Use define & share or set instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
167
        } elseif (is_object($definition)) {
168
            $this->instance($name, $definition); //如果$definition是实例的话则只能单例
169
        } elseif (is_string($definition)) {
170
            $this->bind($name, $definition);
171
            $share && $this->share($name);
172
        } else {
173
            throw new ConfigException(sprintf("Unexpected object definition type '%s'", gettype($definition)));
174
        }
175
        return $this;
176
    }
177
178
    /**
179
     * 如果不能简单获取,则使用设置定义的方式
180
     * @param string $name
181
     * @param Definition $definition
182
     * @param boolean $share
183
     * @return Definition
184
     * @deprecated Will be protected, Use define & share or set instead.
185
     */
186
    public function setDefinition($name, Definition $definition, $share = false)
187
    {
188
        $this->definitions[$name] = $definition;
189
        $share && $this->share($name);
190
        return $definition;
191
    }
192
193
    /**
194
     * 设置类或者类实例的分享
195
     * @param string $name
196
     * @return $this
197
     */
198
    public function share($name)
199
    {
200
        //兼容旧的api,给出移除提示
201
        if (func_num_args() == 2) {
202
            trigger_error("Use set instead, Now share only expects one argument", E_USER_DEPRECATED);
203
            $arguments = func_get_args();
204
            $arguments[] = true;
205
            return call_user_func_array([$this, 'set'], $arguments);
206
        }
207
        $this->shares[$name] = null;
208
        return $this;
209
    }
210
211
    /**
212
     * 为指定类设置一个别名
213
     * @param string $alias
214
     * @param string $original
215
     * @return $this
216
     * @deprecated duplication,use bind instead
217
     */
218
    public function alias($alias, $original)
219
    {
220
        return $this->bind($alias, $original);
221
    }
222
223
    /**
224
     * 以获取一个实例
225
     * @param string $name
226
     * @param array $arguments 传递给类的构造参数,会覆盖预先定义的同名参数
227
     * @return object
228
     */
229
    public function get($name, $arguments = [])
230
    {
231
        //兼容旧的api
232
        if (is_bool($arguments)) {
233
            trigger_error("Argument 'new' has been deprecated", E_USER_DEPRECATED);
234
            $forceNewInstance  = $arguments;
235
            $arguments = [];
236
        } else {
237
            $forceNewInstance = false;
238
        }
239
        //如果单例的话直接返回实例结果
240
        if (isset($this->shares[$name]) && !$forceNewInstance) {
241
            return $this->shares[$name];
242
        }
243
        //如果没有设置实例化指令的代理则认为当前提供的即是class,为其创建一条指向自身的bind
244
        if (!isset($this->definitions[$name])) {
245
            $this->bind($name, $name);
246
        }
247
        $definition = $this->definitions[$name];
248
        if (is_callable($definition)) {
249
            $instance = call_user_func($definition, $this, $arguments);
250
        } elseif ($definition instanceof Definition) {
251
            $instance = $this->createFromDefinition($definition, $arguments);
252
        } elseif (is_object($definition)) {
253
            $instance = $definition;
254
        } else {
255
            list(, $instance) = $this->createReflectionAndInstance($definition, $arguments);
256
        }
257
        //如果设置了单例则缓存实例
258
        if (array_key_exists($name, $this->shares)) {
259
            $this->shares[$name] = $instance;
260
        }
261
        return $instance;
262
    }
263
264
    /**
265
     * 分析类结构并自动解决依赖生成一个类实例
266
     * @param string $name 指定类或者类别名
267
     * @param array $arguments 构造参数,覆盖预定义参数
268
     * @throws DependencyInjectionException
269
     * @return object
270
     * @deprecated
271
     */
272
    public function create($name, $arguments = [])
273
    {
274
        list(, $instance) = $this->createReflectionAndInstance($name, $arguments);
275
        return $instance;
276
    }
277
278
    /**
279
     * 获取所有预定义参数
280
     * @return array
281
     */
282
    public function getParameters()
283
    {
284
        return $this->parameterStore->toArray();
285
    }
286
287
    /**
288
     * 设置预定义参数
289
     * @param array $parameterStore
290
     */
291
    public function setParameters(array $parameterStore)
292
    {
293
        $this->parameterStore->setParameters($parameterStore);
294
    }
295
296
    /**
297
     * 添加预定义参数
298
     * @param array $parameters
299
     */
300
    public function addParameters(array $parameters)
301
    {
302
        $this->parameterStore->addParameters($parameters);
303
    }
304
305
    /**
306
     * 设置参数
307
     * @param $name
308
     * @param mixed $value
309
     */
310
    public function setParameter($name, $value)
311
    {
312
        $this->parameterStore->setParameter($name, $value);
313
    }
314
315
    /**
316
     * 获取参数
317
     * @param $name
318
     * @param null $default
319
     * @return mixed|null
320
     */
321
    public function getParameter($name, $default = null)
322
    {
323
        return $this->parameterStore->getParameter($name, $default);
324
    }
325
326
    /**
327
     * 根据definition创建实例
328
     * @param Definition $definition
329
     * @param array $arguments
330
     * @throws DependencyInjectionException
331
     * @return object
332
     */
333
    protected function createFromDefinition(Definition $definition, array $arguments)
334
    {
335
        $arguments = array_replace($definition->getArguments(), $arguments);
336
        list($reflection, $instance) = $this->createReflectionAndInstance($definition->getClass(), $arguments);
337
        $this->prepareInstance($reflection, $instance, $definition);
338
        return $instance;
339
    }
340
341
    /**
342
     * 构建实例
343
     * @param string $class
344
     * @param array $arguments
345
     * @throws DependencyInjectionException
346
     * @return array
347
     */
348
    protected function createReflectionAndInstance($class, array $arguments)
349
    {
350
        $reflection = $this->reflectClass($class);
351
        if (!$reflection->isInstantiable()) {
352
            throw new DependencyInjectionException(sprintf("Can not instantiate [%s]", $class));
353
        }
354
        $constructor = $reflection->getConstructor();
355
        if (!is_null($constructor)) {
356
            $constructorArgs = $this->resolveFunctionArguments(
357
                $constructor,
358
                $this->resolveParameters($arguments),
359
                $this->getContextBindings($class, $constructor->getName())
360
            );
361
            $instance = $reflection->newInstanceArgs($constructorArgs);
362
        } else {
363
            $instance = $reflection->newInstanceWithoutConstructor();
364
        }
365
        return [$reflection, $instance];
366
    }
367
368
    protected function prepareInstance(\ReflectionClass $reflection, $instance, Definition $definition)
369
    {
370
        // 触发setter函数
371
        foreach ($definition->getMethodCalls() as $method => $methodArguments) {
372
            try {
373
                $reflectionMethod = $reflection->getMethod($method);
374
            } catch (\ReflectionException $e) {
375
                throw new DependencyInjectionException(sprintf(
376
                    "Class '%s' has no method '%s'",
377
                    $definition->getClass(),
378
                    $method
379
                ));
380
            }
381
            //获取该方法下所有可用的绑定
382
            $contextBindings = $this->getContextBindings($reflection->getName(), $method);
0 ignored issues
show
Consider using $reflection->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
383
            $reflectionMethod->invokeArgs(
384
                $instance,
385
                $this->resolveFunctionArguments($reflectionMethod, $methodArguments, $contextBindings)
386
            );
387
        }
388
        // 触发属性
389
        foreach ($definition->getProperties() as $propertyName => $propertyValue) {
390
            if (property_exists($instance, $propertyName)) {
391
                $instance->$propertyName = $propertyValue;
392
            } else {
393
                throw new DependencyInjectionException(sprintf(
394
                    "Class '%s' has no property '%s'",
395
                    $definition->getClass(),
396
                    $propertyName
397
                ));
398
            }
399
        }
400
    }
401
402
    /**
403
     * 处理方法所需要的参数
404
     * @param \ReflectionFunctionAbstract $method
405
     * @param array $arguments
406
     * @param array $contextBindings 该方法定义的所有依赖绑定
407
     * @throws DependencyInjectionException
408
     * @return array
409
     */
410
    protected function resolveFunctionArguments(\ReflectionFunctionAbstract $method, array $arguments, array $contextBindings = [])
411
    {
412
        $functionArguments = [];
413
        $arguments = $this->resolveParameters($arguments);
414
        $isNumeric = !empty($arguments) && is_numeric(key($arguments));
415
        foreach ($method->getParameters() as $parameter) {
416
            //如果提供的是数字索引按照参数位置处理否则按照参数名
417
            $index = $isNumeric ? $parameter->getPosition() : $parameter->getName();
0 ignored issues
show
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
418
            // 如果定义过依赖 则直接获取
419
            if (isset($arguments[$index])) {
420
                $functionArguments[] = $arguments[$index];
421
            } elseif (($dependency = $parameter->getClass()) != null) {
422
                $dependencyName = $dependency->getName();
0 ignored issues
show
Consider using $dependency->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
423
                //如果该依赖已经重新映射到新的依赖上则修改依赖为新指向
424
                isset($contextBindings[$dependencyName]) && $dependencyName = $contextBindings[$dependencyName];
425
                try {
426
                    $functionArguments[] = $this->get($dependencyName);
427
                } catch (DependencyInjectionException $exception) {
428
                    if ($parameter->isOptional()) {
429
                        $functionArguments[] = $parameter->getDefaultValue();
430
                    } else {
431
                        throw $exception;
432
                    }
433
                }
434
            } elseif ($parameter->isOptional()) {
435
                $functionArguments[] = $parameter->getDefaultValue();
436
            } else {
437
                throw new DependencyInjectionException(sprintf(
438
                    'Missing required parameter "%s" when calling "%s"',
439
                    $parameter->getName(),
0 ignored issues
show
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
440
                    $method->getName()
441
                ));
442
            }
443
        }
444
        return $functionArguments;
445
    }
446
447
    /**
448
     * 获取指定类的绑定关系
449
     * [
450
     *     'User' => [
451
     *          'original' => 'SchoolInterface'
452
     *          'bind' => 'MagicSchool',
453
     *     ]
454
     * ]
455
     * @param string $contextClass
456
     * @param string $contextMethod
457
     * @return mixed
458
     */
459
    protected function getContextBindings($contextClass, $contextMethod)
460
    {
461
        if (!isset($this->contextBindings[$contextClass])) {
462
            return [];
463
        }
464
        $contextBindings = isset($this->contextBindings[$contextClass]['general'])
465
            ? $this->contextBindings[$contextClass]['general'] : [];
466
        if (isset($this->contextBindings[$contextClass][$contextMethod])) {
467
            $contextBindings = array_merge($contextBindings, $this->contextBindings[$contextClass][$contextMethod]);
468
        }
469
        return $contextBindings;
470
    }
471
472
    /**
473
     * 预处理参数
474
     * @param $parameters
475
     * @return array
476
     */
477
    protected function resolveParameters($parameters)
478
    {
479
        return array_map(function ($parameter) {
480
            //字符类型参数处理下预定义参数的情况
481
            if (is_string($parameter)) {
482
                $parameter = $this->resolveString($parameter);
483
            } elseif ($parameter instanceof Reference) { //服务依赖
484
                $parameter = $this->get($parameter->getName());
485
            } elseif (is_array($parameter)) {
486
                $parameter = $this->resolveParameters($parameter);
487
            }
488
            return $parameter;
489
        }, $parameters);
490
    }
491
492
    /**
493
     * 处理字符串
494
     * @param $value
495
     * @return mixed
496
     * @throws DependencyInjectionException
497
     */
498
    protected function resolveString($value)
499
    {
500
        //%xx%类型的直接返回对应的参数
501
        if (preg_match("#^%([^%\s]+)%$#", $value, $match)) {
502
            $key = $match[1];
503
            if ($parameter = $this->parameterStore->getParameter($key)) {
504
                return $parameter;
505
            }
506
            throw new DependencyInjectionException(sprintf("Parameter [%s] is not defined", $key));
507
        }
508
        //"fool%bar%baz"
509
        return preg_replace_callback("#%([^%\s]+)%#", function ($matches) {
510
            $key = $matches[1];
511
            if ($parameter = $this->parameterStore->getParameter($key)) {
512
                return $parameter;
513
            }
514
            throw new DependencyInjectionException(sprintf("Parameter [%s] is not defined", $key));
515
        }, $value);
516
    }
517
518
    /**
519
     * 获取类的反射对象
520
     * @param string $className
521
     * @throws DependencyInjectionException
522
     * @return \ReflectionClass
523
     */
524
    protected function reflectClass($className)
525
    {
526
        try {
527
            $reflection = new \ReflectionClass($className);
528
        } catch (\ReflectionException $e) {
529
            throw new DependencyInjectionException(sprintf('Class "%s" is invalid', $className));
530
        }
531
        return $reflection;
532
    }
533
}
534