laravel框架 – 容器、绑定与依赖注入

laravel框架 – 容器、绑定与依赖注入

laravel框架的核心概念就是容器,打开laravel的入口文件public/index.php,一路追寻,会发现laravel框架在初始化时会新建一个Illuminate\Foundation\Application实例对象。这个Application实例就是我们的laravel容器,是整个laravel程序的基础:

屏幕快照 2016-06-21 下午10.44.35

 

一、容器

容器,就是用来装东西的,对吧。现实中的容器可以用来装水、装米,但编程世界中的容器装的则是一个个实例对象。举个例子,在程序运行过程中,我们可能需要一个Mailer实例来发送邮件,通常直观的做法是在需要的时候才创建这个Mailer实例,但我们也可以在程序初始化的时候先行创建一个Mailer实例,并在容器中注册该实例(laravel中叫做binding),随后在程序中任何需要用到Mailer的地方,就可以直接向容器申请这个实例(laravel叫做resolving)或者由容器进行自动依赖注入。

当然,实际的绑定并不是真的绑定一个实例,而是绑定一个闭包函数,该闭包负责生成要绑定的实例对象。同时在绑定的时候定义是否为单例绑定(laravel中把单例绑定叫做shared)。然后直到程序向容器申请该实例的时候,才真正执行这个闭包函数去生成一个实例。对于非单例绑定,每次resolving都会生成一个新的实例;对于单例绑定,只在第一次resolving的时候生成实例,然后将该实例放入容器的instances数组中(Illuminate\Container\Container->instances),后面在申请时直接从该数组中返回实例。

(如果你有java的spring框架基础的话,那么就很好理解了。laravel容器就相当于spring容器,容器中绑定的实例对象相当于spring bean的概念,他们的目的都是为了依赖注入。另外,laravel的中间件也有点类似spring中切面的概念。推荐可以看一下Craig Walls写的《Spring实战》这本书,对于容器与依赖注入的概念描述得很棒。)

(或者,关于容器与依赖注入的概念,还可以参考这篇博文:https://www.insp.top/article/learn-laravel-container

1. binding

绑定操作通常在ServiceProvider的register()方法中定义,然后通过写入config/app.php文件中让laravel框架在初始化时自动加载。(当然,你也可以在程序其他地方直接调用bind()与singleton()方法进行绑定)

绑定的作用其实是告诉容器,在解析绑定时候如何生成需要的实例对象。如果一个类不依赖于任何其他类(即构造方法无参)或者它的构造器依赖可以被laravel解析,那么这个类就无需绑定到容器。容器不需要被告知如何生成这样的类对象,因为容器可以通过php的反射机制自动解析出具体对象。

However, there is no need to bind classes into the container if they do not depend on any interfaces. The container does not need to be instructed on how to build these objects, since it can automatically resolve such “concrete” objects using PHP’s reflection services.

—— laravel官方文档

Container::bind()与Container::singleton()方法定义如下:

2. resolving

获取绑定对象,在程序的任何地方,都可以通过如下三种方法手工获取绑定对象:

可以稍微看一下Container::make()方法的定义:

3. 依赖注入

上面的resolving其实是程序员手动向容器申请解析绑定然后获得实例对象。更为常用的做法是通过在类的构造方法中对参数进行类型提示(type-hint,即定义参数类型)来使得laravel自动注入所依赖的参数对象,这就是laravel的依赖注入。

不幸的是laravel官方文档实在是太渣,对依赖注入的原理只字未提,甚至连用法都没讲清楚=。=#

(1)laravel不仅支持对构造方法进行依赖注入,也支持对某些方法进行依赖注入(比如路由映射到的控制器方法),但laravel依赖注入有个基本前提就是这些方法必须是由laravel容器按照执行逻辑自动调用的。只有这样,laravel才能在调用方法时判断参数,并根据type-hint进行依赖注入。如果你在程序中手动调用这些方法,就别指望laravel帮你依赖注入了,你必须自己在调用时候手动传参。

(3)如果一个类不依赖于任何其他类(即构造方法无参),或者它的构造器依赖可以被laravel解析,那么这个类就无需绑定到容器,laravel可以在需要的时候通过php反射机制自动生成该类实例。

(2)type-hint必须是真实存在的类型。容器在进行依赖注入的时候,会先判断这个type-hint是否存在;如果该type-hint类型不存在,则直接返回ReflectionException;如果存在,再判断是否可以直接生成该类型实例;如果无法直接生成,再查找容器的bindings[]与aliases[]数组,查看是否有该类型的绑定关系;如果有,则从绑定关系中解析出该类型实例。

 

二、举个例子🙌🌰

1. 定义一个测试类

在app\Common目录下定义一个App\Common\Utility类:

2. 尝试绑定与解析

在app\Providers\AppServiceProvider.php的register()方法中绑定Utility类:

然后app\Http\routes.php中添加一个路由方法:

这时再访问http://localhost:8000/bind页面,返回结果如下:

屏幕快照 2016-06-26 上午10.02.11

从上图结果中,我们可以看出,两次请求解析‘utility’绑定得到实例各不相同,而两次请求‘sharedUtility’绑定得到是同一个实例。在Application->bindings[]数组中可以发现这两个绑定;在Application->instances[]数组中,可以看到‘sharedUtility’实例。

3. 尝试依赖注入

3.1 在app\Http\routes.php添加一个路由方法:

这个路由方法有个参数$utl,并且给定了该参数的type-hint为App\Common\Utility类。这样,当laravel根据执行逻辑执行到这个路由方法时,首先尝试解析这个App\Common\Utility类,因为该类型不依赖于其他类(构造器无参),所以laravel通过反射机制直接生成该类型的实例对象,并赋予$utl参数。这样就完成了一次依赖注入!并且是一次无需绑定的依赖注入(你可以事先把第2步中在AppServiceProvider定义的‘utility’与‘sharedUtility’绑定删除掉)。

访问http://localhost:8000/di,结果如下:

屏幕快照 2016-06-26 上午10.30.22

PS:此时,可以用app(‘App\Common\Utility’)来make一个Utility实例,等效于new App\Common\Utility,当然这样make是有些多此一举了,我只是为了说明容器有这个能力。

 

3.2 然后我们修改Utility类的构造器定义,给他加上一个依赖关系,即参数$app:

并且由于我们没有给$app参数一个type-hint,所以laravel容器无法解析出Utility构造方法的依赖来进行自动注入,那么此时再访问我们的di路由就会报错。

 

3.3 为了让laravel能够解析出Utility类,我们必须为其添加一个绑定,修改AppServiceProvider的register()方法:

或者:

这样,当laravel发现无法直接解析出Utility类型后,就会去容器的bindings[]与aliases[]数组中查找有没绑定过该类型,有的话则通过绑定关系解析出该类型的实例,赋值给$utl参数。这就完成了一次通过绑定解析的依赖注入。此时再访问di路由就能正常打印结果了。

屏幕快照 2016-06-26 上午11.05.24

 

三、Service Providers

ServiceProvider的核心是register()方法,在register()方法中只允许绑定对象到容器中,不允许在其中注册事件监听、路由等。

注意:ServiceProvider本身并不提供服务,实际提供“服务”的其实是绑定的对象。

 

四、Facades

Facades提供一种使用静态接口的方式来调用容器中注册的实例对象。

1. 用例

举个例子,假如容器中已绑定Illuminate\Cache\CacheManage对象到’cache’名字上。现在我们要调用他的get方法,可以这样写:

这时候Facades跳出来说,我可以这么写:

2. 运行机制

20150624120315307

不多说了直接参考该文章:http://blog.csdn.net/hel12he/article/details/46620519

 

PS:其实我有一个疑问,java中用到容器是为了让那些常用的对象可以常驻内存,不用在每次请求的时候初始化,对吧?但php是脚本语言,执行完请求就释放内存了呀?每次请求不都照样得初始化容器,绑定“bean”,然后执行请求,执行完后又释放掉。既然无法常驻内存,那还要容器用来干嘛?=。=#

2016.06.22 好吧,其实spring容器的根本目的也是为了实现依赖注入,并不是为了让spring bean常驻内存,只是默认情况下spring bean对象是以单例模式常驻在spring容器的内存中。但是spring bean可选有几种生命周期,比如request模式的spring bean就相当于laravel容器中绑定的单例对象了。

One thought on “laravel框架 – 容器、绑定与依赖注入

发表评论

电子邮件地址不会被公开。 必填项已用*标注