看源代码了解 Container
自从上文《看 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 输出到文件中。我们可以可以这么写:
在需要的地方直接调用:
写个测试用例:
如果这时候我们需要将 log 输出到钉钉上,我们重新写一个 LogToDD 类:
这时候,我们还需要修改使用端 (UseLogger) 代码,让它引入 LogToDD 类:
其实到这,你就能「嗅出」坏代码来了:
假如我使用端特别多,那就意味着每个地方我都要做引入修改。
根据 DIP 原则,我们应该面向接口开发。让使用端依赖接口,而不是实现。所以我们创建一个接口:
然后让 LogToFile
和 LogToDD
作为 Logger
的实现:
这样我们在使用端时,直接引入 Logger
接口,让代码剥离具体实现。
这样就可以保证,无论是使用文件保存,还是下发到钉钉上,都可以不用去改代码了,只需要再真正调用的时候,随着业务需要自行选择。如测试:
结果:
但这里有个问题,最后在实例化使用时,还是要「硬编码」的方式 new
我们的实现类 (LogToDD or LogToFile)。
那有没有办法更进一步把最后的 new LogToDD()
也控制反转了呢?
绑定 的 实现
这里我们把实现类绑定在 interface 或者标识 key 上,只要解析这个 interface 或者 key,就可以拿到我们的实现类。
我们来写一个简单的类来达到绑定和解析的功能:
我们可以测试下:
只要有一处绑定了 Logger
和 LogToFile
的关系,就可以在任何需要调用的地方直接解析引用了。
也就意味着,通过这种方法,在所有编码的地方都是引入「interface」,而不是实现类。彻底实现 DPI 原则。
当我们把所有这种绑定聚集在一起,就构成了我们今天的主题内容:「Container」—— illuminate / container。
Laravel Container
在研究 Laravel Container 之前,我们根据上面的例子,使用 Container,看怎么方便实现。
达到一样的效果
[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 的作用主要有两个,一个是绑定,另个一个是解析。
绑 定
我们看看主要有哪些绑定类型:
绑定一个单例
绑定实例
绑定接口到实现
绑定初始数据
情境绑定
tag 标记绑定
下面我们根据这些类型进行分析:
1. 绑定一个单例
主要利用参数 $share = true 来标记此时绑定为一个单例。
2. 绑定实例
绑定实例,主要是将 [$abstract, $instance]
存储进数组 $instances
中。
3. tag 标记绑定
这个挺好理解,主要是将 $abstracts
数组放在同一组标签下,最后可以通过 tag,解析这一组 $abstracts
。
4. 绑定
解析
我们一步步来分析该「解析」函数:
该方法主要是区分,解析的对象是否有参数,如果有参数,还需要对参数做进一步的分析,因为传入的参数,也可能是依赖注入的,所以还需要对传入的参数进行解析;这个后面再分析。
如果是绑定的单例,并且不需要上面的参数依赖。我们就可以直接返回 $this->instances[$abstract]
。
这一步主要是先从绑定的上下文找,是不是可以找到绑定类;如果没有,则再从 $bindings[]
中找关联的实现类;最后还没有找到的话,就直接返回 $abstract
本身。
这个比较好理解,如果之前找到的 $concrete
返回的是 $abstract
值,或者 $concrete
是个闭包,则执行$this->build($concrete)
,否则,表示存在嵌套依赖的情况,则采用递归的方法执行 $this->make($concrete)
,直到所有的都解析完为止。
$this->build($concrete)
此方法分成两个分支:如果 $concrete instanceof Closure
,则直接调用闭包函数,返回结果:$concrete()
;另一种分支就是,传入的就是一个 $concrete === $abstract === 类名
,通过反射方法,解析并 new 该类。
具体解释看上面的注释。 ReflectionClass 类的使用,具体参考:https://php.golaravel.com/class.reflectionclass.html
代码往下看:
这些就比较好理解了。主要判断是否存在扩展,则相应扩展功能;如果是绑定单例,则将解析的结果存到 $this->instances
数组中;最后做一些解析的「善后工作」。
最后我们再看看 tag 标签的解析:
现在看这个就更好理解了,如果传入的 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
- 点赞
- 收藏
- 关注作者
评论(0)