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

Container::bindDefinition()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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