php之控制反转容器(Ioc Container)

上一节文末,我们抛出了一个问题:如何解决注入引起的更复杂的问题?

有些人可能还没明白这个问题是怎么回事。

我们来详细的描述一下:

首先我们假设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
15
Class 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
11
class 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
4
EmailSenderBy163 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
19
Class 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
15
class 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
16
EmailSenderBy163 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
29
Class 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
8
User Object
(
[_emailSenderObject:User:private] => EmailSenderBy163 Object
(
[_name:EmailSenderBy163:private] =>
)

)

本节课我们就说这么多