上一节文末,我们抛出了一个问题:如何解决注入引起的更复杂的问题?
有些人可能还没明白这个问题是怎么回事。
我们来详细的描述一下:
首先我们假设A依赖B和C,B依赖D,C依赖E,D依赖F,现在我们来看看怎么实例化A?1
$a = new A(new B(new D(new F)), new C(new E()));
是不是感觉要崩溃了,实例化A之前就得先知道它依赖哪些类。
从一开始我们为了解决依赖的问题,到现在依赖却成了最棘手的问题,怎么办?
有人提出:如果我们也有一个类似composer的管理容器,在实例化A的时候,能够解决各个类之间的依赖问题是不是就很ok了?
没错,就是这样。
php有一个IoC容器的概念,其作用,就是帮助我们解决这个问题。
IoC容器又是什么?所谓的IoC容器呢,并没有多么高大上,它其实就是一个类,为了解决依赖问题,IoC应该提供如下功能:
存储定义的类
实例化类
下面来看一个简版的实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Class Container
{
private $_definitions;
public function set($class, $definition)
{
$this->_definitions[$class] = $definition;
}
public function get($class, $params = [])
{
$definition = $this->_definitions[$class];
return call_user_func($definition, $params);
}
}
Container类封装的很简单,set方法用于存储类,get用于实例化类。
还以上一节用户发送邮件为例1
2
3
4
5
6
7
8
9
10
11class EmailSenderBy163
{
private $_name;
public function __construct($name = '')
{
$this->_name = $name;
}
public function send()
{
}
}
我们看一下如何借用IoC容器操作1
2
3
4
5
6
7
8
9$container = new Container;
$container->set('EmailSenderBy163', function ($name = '') {
return new EmailSenderBy163($name);
});
$emailSenderBy163 = $container->get('EmailSenderBy163', '163');
echo "<pre>";
print_r($emailSenderBy163);
结果1
2
3
4EmailSenderBy163 Object
(
[_name:EmailSenderBy163:private] => 163
)
简单分析下上面代码的含义
首先我们定义了一个容器类 Container
定义了一个私有属性 $_definitions, 用于保存定义的类
set 方法,添加类及其定义到 $_definitions 属性中,第一个参数是类名,第二个参数这里是一个回调函数,用于创建类 $class
get 方法,传递类名,通过 属性 $_definitions 找到类的定义,并调用 call_user_func 函数调用该定义, 第二个参数 $params 是set时第二个参数即回调函数的参数
当然,这只是一个简版的实现
下面我们扩展一下,如果要实现有依赖其他对象的类,该如何实现呢?
我们把Container的get方法整理一下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19Class Container
{
private $_definitions;
public function set($class, $definition)
{
$this->_definitions[$class] = $definition;
}
public function get($class, $params = [])
{
if (isset($this->_definitions[$class]) && is_callable($this->_definitions[$class], true)) {
$definition = $this->_definitions[$class];
return call_user_func($definition, $this, $params);
} else {
throw new Exception("error");
}
}
}
注意同刚才的get方法的区别。相比于简版的实现,get方法在判断回调函数上趋于严谨。
User类定义如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class User
{
private $_emailSenderObject;
public function __construct(EmailSenderBy163 $emailSenderObject)
{
$this->_emailSenderObject = $emailSenderObject;
}
public function register()
{
// other code
$this->_emailSenderObject->send();
}
}
注意看User类,我们想在实例化User的时候,通过构造函数“注入” EmailSenderBy163 的对象,并赋值给User::$_emailSenderObject属性。
来看看此时我们应该如何用Container实例化 EmailSenderBy163 和 User 。1
2
3
4
5
6
7
8
9
10
11
12$container = new Container;
$container->set('EmailSenderBy163', function ($container, $name = '') {
return new EmailSenderBy163($name);
});
$container->set('User', function ($container, $params = []) {
return new User($container->get($params[0], $params[1]));
});
echo '<pre>';
print_r($container->get('EmailSenderBy163', ['163']));
print_r($container->get('User', ['EmailSenderBy163', '163']));
结果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16EmailSenderBy163 Object
(
[_name:EmailSenderBy163:private] => Array
(
[0] => 163
)
)
User Object
(
[_emailSenderObject:User:private] => EmailSenderBy163 Object
(
[_name:EmailSenderBy163:private] => 163
)
)
似乎没什么问题,一个类依赖的其他对象我们也解决了。
但是如果我们要解决文中一开始抛出的问题,还是需要先把所有的类全部set进去,然后get的时候指定其所依赖的类,依然很麻烦。
上一节文末我们让大家课后看一下反射的知识,不知道你熟不熟悉?
我们简单的聊两句:反射,是php5增加的功能,通过反射,可以导出或提取出关于类、方法、属性、参数等的详细信息。
也就是说,我们可以利用反射相关的一系列API,来分析类所依赖的对象,并做自动实例化处理。
限于篇幅,反射相关的API我们就不多做介绍了,不熟悉的可以看一下手册,了解即可。
下面我们基于反射,让我们的Container更强悍一些。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29Class Container
{
public function get($class, $params = [])
{
return $this->build($class, $params);
}
public function build($class, $params)
{
$dependencies = [];
$reflection = new ReflectionClass($class);
$constructor = $reflection->getConstructor();
if ($constructor !== null) {
foreach ($constructor->getParameters() as $param) {
$c = $param->getClass();
if ($c !== null) {
$dependencies[] = $this->get($c->getName());
}
}
}
foreach ($params as $index => $param) {
$dependencies[$index] = $param;
}
return $reflection->newInstanceArgs($dependencies);
}
}
同样是User类,现在我们可以这样实例化了1
2
3
4$container = new Container;
$user = $container->get('User');
echo '<pre>';
print_r($user);
结果1
2
3
4
5
6
7
8User Object
(
[_emailSenderObject:User:private] => EmailSenderBy163 Object
(
[_name:EmailSenderBy163:private] =>
)
)
本节课我们就说这么多