在写之前,我要吐槽一番,laravel的官方文档写的是真恶心,上文不接下文,看的人头皮发麻。
先来说说laravel中的几个核心概念
服务容器
Laravel 服务容器是一个用于管理类依赖和执行依赖注入的强大工具。依赖注入听上去很花哨,其实质是通过构造函数或者某些情况下通过「setter」方法将类依赖注入到类中。
简单的说服务容器就是管理类的依赖和执行依赖注入的工具,这是官方文档上说的。
但是我的理解更偏向于:
一段生命周期所抽象的一个对象
很难理解,打个比方,在一次请求中,你可能会用到很多服务,比如路由,队列,中间件,自定义服务等等。那么如何这么多的服务,如果不能妥善管理,势必会造成各种混乱,这是你不希望看到的,于是你想出了一个办法,用一个篮子,来乘放这些服务,然后使用的时候从篮子中拿就行了。
说到服务容器,就需要说服务容器的两个重要概念IOC(控制反转)和DI(依赖注入)。
这两个概念并不是laravel中特有的。目的是为了降低软件开发的复杂度;提升模块低耦合、高内聚的一种设计模式。
- 控制反转:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源;
- 依赖注入:应用程序依赖容器创建并注入它所需要的外部资源。
好的,我们知道了控制反转的作用就是实现模块或对象的解耦,通过借助第三方将具有依赖的模块或对象进行解耦,而这个第三方,就是IOC容器。
引入了IoC容器后,对象A、B、C、D之间没有了依赖关系,全体齿轮转动的控制权交给IoC容器。这时候齿轮转动控制权不属于任何对象,而属于IoC容器,所以控制权反转了,从某个对象转到了IoC容器。
而对于依赖注入,其实首先要明白依赖就是一种关系。如果在类A中创建了类B的实例,就可以说A依赖于B。那么直接在需要依赖的类中声明被依赖的类的实例的方法,会出现下面问题:
- 如果需要修改类B的创建方式,那么需要修改类A中对B的声明代码
- 如果想测试不同B对象对A的影响很困难,因为B的初始化被写死在了A的构造函数中
- 如果要对类B的实例进行调试时,就必须在类A中对类B的实例进行测试,增加了测试难度和复杂度
如果将类B实例作为类A构造器参数进行传入,在调用A的构造器前,B实例已经初始化好了,像这种不是自己主动初始化依赖,而是通过外部传入依赖对象的方式,就是依赖注入。
解释完IOC和DI的概念,我们在回想一下刚刚将服务放在篮子中的例子,那么现在就有两个问题了:
- 如何将服务放在篮子里?
- 需要某个服务的时候,如果从篮子中拿出来?
这就是laravel中服务容器的两个基本作用:
- 服务绑定
- 服务解析
对于具体的绑定/解析方法,官方文档上已经说的很详细了,这里就不举例了,还是对于概念的理解。
服务绑定
一个类要被容器提取,首先要先注册到这个容器。laravel叫这个容器为服务容器,那么我们需要某个服务,就得先注册、绑定这个服务到容器中,那么提供服务并绑定服务到容器的东西,就是服务提供者了。
注:如果一个类没有基于任何接口那么就没有必要将其绑定到容器。容器并不需要被告知如何构建对象,因为它会使用PHP的反射服务自动解析出具体的对象。
服务解析
注册、绑定服务后,就可以根据需要从IOC容器中提取出对应的服务了。就是一个“拿出来”的过程。同样的,具体方法可以自行查看官方文档。
服务提供者
由服务绑定,我们引出了laravel的另外一个核心概念----服务提供者。我们知道,服务容器是“乘放”服务的地方,而服务提供者就是我们将服务放到服务容器的地方。简单说服务提供者就是把服务注册、绑定到容器上的地方。根据官方文档,就是:
服务提供者是Laravel应用启动的中心,你自己的应用以及所有Laravel的核心服务都是通过服务提供者启动。当然启动这些服务,需要加载注册服务,包括注册服务容器绑定、事件监听器、中间件甚至路由。服务提供者是应用配置的中心。
所有的服务提供者都有一个共同的父类Illuminate\Support\ServiceProvider类,这是一个抽象类,要求所有的服务提供者至少需要实现一个register方法,在这个方法中应该只绑定内容到服务容器。
注:永远不要在register方法中尝试在其中注册任何的事件监听,路由或者其它功能。因为我们可能使用了还未注册的服务,从而导致异常。
如何理解这句话的含义呢?
如果有了解过服务容器,就会知道在「绑定」操作仅仅是建立起接口和实现的对应关系,此时并不会创建具体的实例,即不会存在真实的依赖关系。直到某个服务真的被用到时才会从「服务容器」中解析出来,而解析的过程发生在所有服务「注册」完成之后。
一旦我们尝试在 register 注册阶段使用某些未被加载的服务依赖,即这个服务目前还没有被注册所以不可用。
我们可以随意打开一个服务提供者看看:
namespace Illuminate\Cache;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Support\DeferrableProvider;
class CacheServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('cache', function ($app) {
return new CacheManager($app);
});
$this->app->singleton('cache.store', function ($app) {
return $app['cache']->driver();
});
$this->app->singleton('memcached.connector', function () {
return new MemcachedConnector;
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [
'cache', 'cache.store', 'memcached.connector',
];
}
}
可以看到,在register中只是完成了三个简单的注册。
OK,通过register,我们将服务可以注册绑定到服务容器中,那么当所有的服务都被注册之后,我们需要使用某些服务时,应该在哪里使用呢?服务提供者模版提供了一个boot方法。
boot方法在所有服务提供者被注册以后才会被调用,这就是说我们可以在其中访问框架已注册的所有其它服务。
boot方法在register方法之后调用,这就意味着我们无需在注入某个实例的时候, 它还没有绑定或者实例化。
boot中一般完成启动相应的服务的工作,比如需要处理依赖关系的业务逻辑时,就可以将这些业务逻辑放在boot当中,或者是注册事件监听器,引入路由等你能想到的业务逻辑。
声明完成服务提供者后,添加到config/app.php中的providers数组中进行注册,而在providers数组中默认的注册了laravel中所有的核心服务,这些提供者启动了laravel的核心组件,如邮件,队列,缓存等。
Facades
Facades,我们可以叫做门面,其实就是一组静态接口或者代理,能让开发者简单的访问绑定到容器中的各种服务。
Facades 为应用程序的 服务容器 中可用的类提供了一个「静态」接口。Laravel 本身附带许多的 facades,甚至你可能在不知情的状况下已经在使用他们!Laravel 「facades」作为在服务容器内基类的「静态代理」,拥有简洁、易表达的语法优点,同时维持着比传统静态方法更高的可测试性和灵活性。
举个例子,我们熟知的Route,其实就是一个Facades,它其实就是Illuminate\Support\Facades\Route类的别名,这个facades类代理的是注册到服务容器中的router服务,所以通过Route类就可以很方便的使用router服务中的各种服务,而其中的服务解析完全是进行自动的隐式解析的。至于怎么解析,我们之后会看一看源码,了解一下其流程。
'Route' => Illuminate\Support\Facades\Route::class,
由此,我们可以理解Facade和服务提供者是紧密配合的,所以如果以后自己写Laravel自定义服务时除了通过组件的Service Provider将服务注册进服务容器,还可以在组件中提供一个Facade让应用程序能够方便的访问你写的自定义服务。