看源代码了解 Container

举报
xenia 发表于 2019/10/12 15:42:52 2019/10/12
【摘要】 自从上文《看 Laravel 源代码了解 ServiceProvider 的加载》,我们知道 Application (or Container) 充当 Laravel 的容器,基本把所有 Laravel 核心的功能纳入这个容器里了。我们今天来看看这个 Application / Container 到底是什么东西?了解 Container 之前,我们需要先简单说说 Inversion of...

自从上文《看 Laravel 源代码了解 ServiceProvider 的加载》,我们知道 Application (or Container) 充当 Laravel 的容器,基本把所有 Laravel 核心的功能纳入这个容器里了。

我们今天来看看这个 Application / Container 到底是什么东西?

了解 Container 之前,我们需要先简单说说 Inversion of Control (控制反转) 的原理。

Inversion of Control Control Control

要知道什么是 Inversion of Control 之前,我们最好先了解一个原则:

依赖倒转原则 (Dependence Inversion Priciple, DIP)提倡:

  • 高层模块不应该依赖底层模块。两个都应该依赖抽象

  • 抽象不应该依赖细节,细节应该依赖抽象

  • 针对接口编程,不要针对实现编程

在编程时,我们对代码进行模块化开发,它们之间避免不了有依赖,如模块 A 依赖模块 B,那么根据 DIP,模块 A 应该依赖模块 B 的接口,而不应该依赖模块 B 的实现。

下面我们举个例子。

我们需要一个 Logger 功能,将系统 log 输出到文件中。我们可以可以这么写:

30.png

在需要的地方直接调用:

31.png

写个测试用例:

32.png

如果这时候我们需要将 log 输出到钉钉上,我们重新写一个 LogToDD 类:

33.png

这时候,我们还需要修改使用端 (UseLogger) 代码,让它引入 LogToDD 类:

34.png

其实到这,你就能「嗅出」坏代码来了:

假如我使用端特别多,那就意味着每个地方我都要做引入修改。

根据 DIP 原则,我们应该面向接口开发。让使用端依赖接口,而不是实现。所以我们创建一个接口:

1.png

然后让 LogToFile 和 LogToDD 作为 Logger 的实现:

2.png

这样我们在使用端时,直接引入 Logger 接口,让代码剥离具体实现。

3.png

这样就可以保证,无论是使用文件保存,还是下发到钉钉上,都可以不用去改代码了,只需要再真正调用的时候,随着业务需要自行选择。如测试:

4.png

结果:

但这里有个问题,最后在实例化使用时,还是要「硬编码」的方式 new 我们的实现类 (LogToDD or LogToFile)。

那有没有办法更进一步把最后的 new LogToDD() 也控制反转了呢?

绑定 的 实现

这里我们把实现类绑定在 interface 或者标识 key 上,只要解析这个 interface 或者 key,就可以拿到我们的实现类。

我们来写一个简单的类来达到绑定和解析的功能:

5.png

我们可以测试下:

6.png

只要有一处绑定了 Logger 和 LogToFile 的关系,就可以在任何需要调用的地方直接解析引用了。

也就意味着,通过这种方法,在所有编码的地方都是引入「interface」,而不是实现类。彻底实现 DPI 原则。

当我们把所有这种绑定聚集在一起,就构成了我们今天的主题内容:「Container」—— illuminate / container。

Laravel Container

在研究 Laravel Container 之前,我们根据上面的例子,使用 Container,看怎么方便实现。

7.png

达到一样的效果

[2018-05-19 15:36:30] testing.INFO: log the message to a file :yemeishu

注:在 Laravel 开发时,我们会把这个绑定写在 APPServiceProvider 的 boot 或者 register 中,或者其他的 ServiceProvider 上也行。

结合上一篇文章《看 Laravel 源代码了解 ServiceProvider 的加载》和以上的原理的讲解。我们对 Container 的使用,已经不陌生了。

接下来就可以看看 Container 的源代码了。

Container 源码 解 析

从上文可以知道,Container 的作用主要有两个,一个是绑定,另个一个是解析。

绑 定

我们看看主要有哪些绑定类型:

  1. 绑定一个单例

  2. 绑定实例

  3. 绑定接口到实现

  4. 绑定初始数据

  5. 情境绑定

  6. tag 标记绑定

下面我们根据这些类型进行分析:

1. 绑定一个单例

9.png

主要利用参数 $share = true 来标记此时绑定为一个单例。

2. 绑定实例

10.png

绑定实例,主要是将 [$abstract, $instance]存储进数组 $instances 中。

3. tag 标记绑定

11.png

这个挺好理解,主要是将 $abstracts 数组放在同一组标签下,最后可以通过 tag,解析这一组 $abstracts

4. 绑定

12.png

13.png

解析

14.png

15.png

16.png

17.png


我们一步步来分析该「解析」函数:

18.png

该方法主要是区分,解析的对象是否有参数,如果有参数,还需要对参数做进一步的分析,因为传入的参数,也可能是依赖注入的,所以还需要对传入的参数进行解析;这个后面再分析。

19.png

如果是绑定的单例,并且不需要上面的参数依赖。我们就可以直接返回 $this->instances[$abstract]

20.png

21.png

这一步主要是先从绑定的上下文找,是不是可以找到绑定类;如果没有,则再从 $bindings[] 中找关联的实现类;最后还没有找到的话,就直接返回 $abstract 本身。


22.png

这个比较好理解,如果之前找到的 $concrete 返回的是 $abstract 值,或者 $concrete 是个闭包,则执行$this->build($concrete),否则,表示存在嵌套依赖的情况,则采用递归的方法执行 $this->make($concrete),直到所有的都解析完为止。

$this->build($concrete)

23.png

24.png

25.png

此方法分成两个分支:如果 $concrete instanceof Closure,则直接调用闭包函数,返回结果:$concrete();另一种分支就是,传入的就是一个 $concrete === $abstract === 类名,通过反射方法,解析并 new 该类。

具体解释看上面的注释。 ReflectionClass 类的使用,具体参考:https://php.golaravel.com/class.reflectionclass.html

代码往下看:

26.png

27.png


这些就比较好理解了。主要判断是否存在扩展,则相应扩展功能;如果是绑定单例,则将解析的结果存到 $this->instances 数组中;最后做一些解析的「善后工作」。

最后我们再看看 tag 标签的解析:

28.png

现在看这个就更好理解了,如果传入的 tag 标签值存在 tags 数组中,则遍历所有 $abstract, 一一解析,将结果保存数组输出。

总结

虽然 Container 核心的内容我们了解了,但还有很多细节值得我们接着研究,如:$alias 相关的,事件相关的,扩展相关的。

最后收尾,推荐大家看看这个 Container:silexphp/Pimple

A small PHP 5.3 dependency injection container https://pimple.symfony.com

未完待续


本文转载自异步社区

原文链接:https://www.epubit.com/articleDetails?id=N17f25841-5a23-4b8f-8b41-780ba91add61

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。