众所周知,Laravel 控制反转 (IoC) / 依赖注入 (DI) 的功能很是强大。遗憾的是, 官方文档 并无详细讲解它的全部功能,因此我决定本身实践一下,并整理成文。下面的代码是基于 Laravel 5.4.26 的,其余版本可能会有所不一样。php
我在这里不会详细讲解依赖注入/控制反转的原则 - 若是你对此还不是很了解,建议阅读 Fabien Potencier (Symfony 框架的创始人)的 What is Dependency Injection? 。html
经过 Laravel 访问 Container 实例的方式有不少种,最简单的就是调用辅助函数 app()
:laravel
为了突出重点 Container 类,这里就不赘述其余方式了。git
注意: 官方文档中使用的是 $this->app
而不是 $container
。github
(* 在 Laravel 应用中,Application 其实是 Container 的一个子类 ( 这也说明了辅助函数 app()
的由来 ),不过在这篇文章中我仍是将重点讲解 Container 类的方法。)sql
想要不基于 Laravel 使用 Container,安装 而后:shell
最简单的用法是经过构造函数注入依赖类。数组
使用 Container 的 make()
方法实例化 MyClass
类:缓存
container 会自动实例化依赖类,因此上面代码实现的功能就至关于:服务器
( 假设 AnotherClass
还有须要依赖的类 - 在这种状况下,Container 会递归式地实例化全部的依赖。)
phper在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提高,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货须要的能够免费分享给你们须要的(点击→)个人官方群677079770
下面是一些基于 PHP-DI 文档 的例子 - 将发送邮件与用户注册的代码解耦:
经过 Container 类,咱们能够轻松实现从接口到具体类到实例的过程。首先定义接口:
声明实现接口的具体类,具体类还能够依赖其余接口( 或者是像上个例子中的具体类 ):
而后使用 bind()
方法把接口与具体类进行绑定:
最后,在 make()
方法中,使用接口做为参数:
注意: 若是没有将接口与具体类进行绑定操做,就会报错:
这是由于 container 会尝试实例化接口 ( new MyInterface
),这自己在语法上就是错误的。
可更换的缓存层:
也能够与抽象类进行绑定:
或者将具体类与其子类进行绑定:
在使用 bind()
方法进行绑定操做时,若是某个类须要额外的配置,还经过闭包函数来实现:
每次带着配置信息建立一个 MySQLDatabase 类的实例的时候( 下面后讲到如何经过 Singletons 建立一个能够共享的实例),都要用到 Database 接口。咱们看到闭包函数接收了 Container 的实例做为参数,若是须要的话,还能够用它来实例化其余类:
还能够经过闭包函数自定义要如何实例化某个类:
可使用 resolving()
方法来注册一个回调函数,当绑定被解析的时候,就调用这个回调函数:
全部的注册的回调函数都会被调用。这种方法也适用于接口和抽象类:
还能够注册一个任何类被解析时都会被调用的回调函数 - 可是我想这可能仅适用于登陆和调试:
你还可使用 extend()
方法把一个类与另外一个类的实例进行绑定:
这里返回的另一个类应该也实现了一样的接口,不然会报错。
只要使用 bind()
方法进行绑定,每次用的时候,就会建立一个新的实例( 闭包函数就会被调用一次)。为了共用一个实例,可使用 singleton()
方法来代替 bind()
方法:
或者是闭包:
为一个具体类建立单例,就只传这个类做为惟一的参数:
在以上的每种状况下,单例对象都是一次建立,反复使用。若是想要复用的实例已经生成了,则可使用 instance()
方法。例如,Laravel 就是用这种方式来确保Container 的实例有且仅有一个的:
其实,你可使用任意字符串做为绑定的名称,而不必定非要用类名或者接口名 - 可是这样作的弊端就是不能使用类名实例化了,而只能使用 make()
方法:
为了同时支持类和接口,而且简化类名的写法,可使用 alias()
方法:
你也可使用 container 来存储任何值 - 好比:配置数据:
支持以数组的形式存储:
在经过闭包进行绑定的时候,这种存储方式就显示出其好用之处了:
( Laravel 框架没有用 container 来存储配置文件,而是用了单独的 Config 类 - 可是 PHP-DI 用了)
小贴士: 在实例化对象的时候,还能够用数组的形式来代替 make()
方法:
到目前为止,咱们已经看了不少经过构造函数进行依赖注入的例子,其实,Laravel 还支持对任何方法作依赖注入:
除了依赖类,还能够传其余参数:
可用于任何可调用的方法:
经过这种语法结构 ClassName@methodName
,就 能够达到实例化一个类并调用其方法的目:
容器用于实例化类,这意味着:
例如,这将会启做用:
最后,你能够将「默认方法」做为第三个参数。若是第一个参数是一个没有指定方法的类名,则将调用默认的方法。 Laravel 使用 事件处理 来实现:
可使用 bindMethod()
方法重写方法调用,例如传递其余参数:
全部这些都会奏效,调用闭包而不是的原始方法:
可是, call()
的任何附加参数都不会传递到闭包中,所以不能使用它们。
注意: 这个方法不属于 容器接口, 只是具体的 容器类. 参考 提交的 PR 了解为何忽略参数。
有时候,你但愿在不一样的地方使用接口的不一样实现。下面是来自 Laravel 文档 中的一个例子:
如今, PhotoController 和 VideoController 均可以依赖于文件系统接口,可是每一个都将接收不一样的实现。你还能够为 give()
使用闭包,就像使用 bind()
同样:
或者命名依赖项:
你还能够经过将变量名称传递给 needs()
(而不是接口)并将值传递给 give()
来绑定基本类型(字符串,整数等):
您可使用闭包来延迟检索值,直到须要它:
在这里你不能传递一个类或一个命名的依赖项(例如 give('database.user')
)由于它将做为文字值返回 - 为此你必须使用一个闭包:
你可使用容器 tag
来绑定相关标记:
而后将全部标记的实例检索为数组:
tag()
的参数都接受数组:
*Note: 这是一个更高级的,只是不多须要-请随意跳过它! *
在绑定或实例已经被使用后须要更改时,能够调用 rebinding()
回调 - 例如,此 Session
类在被 Auth
类使用后被替换,所以须要通知 Auth
类变化:
还有一个快捷方法 refresh()
来处理这个常见模式:
它还返回现有实例或绑定(若是有的话),所以您能够这样作:
(就我的而言,我发现这种语法更加混乱,而且更喜欢上面更详细的版本!)
Note: 这些方法不属于 Container interface, 只有具体 Container class.
makeWith()
方法容许你将其余参数传递给构造函数。 它忽略任何现有的实例或单例,而且在建立具备不一样参数的类的多个实例时仍然有用,同时仍然注入依赖项:
Note: 在Laravel 5.3及如下版本中,它很简单 make($class, $parameters)
. 它是在 Laravel 5.4 被移除, 但后来 从新添加为 makeWith() 在 5.4.16. 在Laravel 5.5中,它彷佛将恢复为Laravel 5.3语法.
这涵盖了我认为有用的全部方法 - 但只是为了解决问题,这里是剩下的公共方法的摘要......
若是类或名称已与 bind()
, singleton()
, instance()
or alias()
绑定,则 bound()
返回true。
它能够用 unset()
重置,它删除指定的绑定/实例/别名。
bindIf()
与 bind()
作一样的事情,除了它只注册一个绑定(若是尚未)(参考上面的 bound()
)。 它可能用于在包中注册默认绑定,同时容许用户覆盖它。
没有 singletonIf()
方法,但你可使用 bindIf($abstract, $concrete, true)
代替:
或者这样写全也能够:
若是已经解析了类 resolved()
则返回true。
我不肯定它有什么用处,若是使用 unset()
它会被重置 (能够看上面的 bound()
)。
factory()
方法返回一个不带参数的闭包,并调用 make()
。
我不肯定它有什么用处...
wrap()
方法包装一个闭包,以便在执行时注入它的依赖项。 wrap 方法接受一组参数, 返回的闭包没有参数:
我不肯定它有什么用处,由于闭包没有参数...
Note: 这种方法不属于 Container interface, 只属于 Container class.
afterResolving()
方法与 resolving()
彻底相同,只是在「解析」回调以后调用 「解析后」 回调。 我不肯定何时会有用...
isShared()
- 肯定给定类型是否为共享单例/实例isAlias()
- 肯定给定字符串是不是已注册的别名hasMethodBinding()
- 肯定容器是否具备给定的方法绑定getBindings()
- 检索全部已注册绑定的原始数组getAlias($abstract)
- 解析基础类/绑定名称的别名forgetInstance($abstract)
- 清除单个实例对象forgetInstances()
- 清除全部实例对象flush()
- 清除全部绑定和实例,有效地重置容器setInstance()
- 替换 getInstance()
使用的实例(Tip:使用 setInstance(null)
清除它,因此下次它将生成一个新实例)Note: 最后一节中没有一个方法是其中的一部分 Container interface.