Laravel的Container/ServiceProvider/Facade之间的关系

这两个概念对于 Laravel
的使用者来说应该并不陌生,尤其是当你希望扩展或者替换 Laravel
核心库的时候,理解和合理使用它们可以极大提升 Laravel
的战斗力。这里以创建一个自己的 ServiceProvider 为例理解 Inversion of
Control 和 Facade 在 Laravel 中的应用。

简述

控制反转(Inversion of Control)

当你接触一段时间Laravel的Service Container, Service
Provider,Contracts和Facade后,也许已经知道它们是什么了,但是对于如何使用,在什么时候使用,以及它们之间的关系是什么,还不是非常清楚。
而关键是如果你反复看文档,你会被它坑死,因为文档有些部分不但没有解释清楚,反而有误导的内容;
现在我们就来一次性把它们搞定;

什么是 IoC

控制反转(Inversion of
Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency
Injection,简称DI),还有一种方式叫“依赖查找”(Dependency
Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。
— 维基百科

简单说来,就是一个类把自己的的控制权交给另外一个对象,类间的依赖由这个对象去解决。依赖注入属于依赖的显示申明,而依赖查找则是通过查找来解决依赖。

基本概念

Laravel 中的使用

注入一个类:

App::bind('foo', function($app)
{
    return new FooBar;
});

这个例子的意思是创建一个别名为 foo 的类,使用时实际实例化的是 FooBar

使用这个类的方法是:

$value = App::make('foo');

$value 实际上是 FooBar 对象。

如果希望使用单例模式来实例化类,那么使用:

App::singleton('foo', function()
{
    return new FooBar;
});

这样的话每次实例化后的都是同一个对象。

注入类的更多例子可以看 Laravel 官网

你可能会疑问上面的代码应该写在哪儿呢?答案是你希望他们在哪儿运行就写在哪儿。0
—— 0 知道写哪儿还用来看这种基础文章么!

在继续本教程之前,你需要先对以上概念有基本了解,知道它们是什么;

服务提供器 (Service Providers)

为了让依赖注入的代码不至于写乱,Laravel 搞了一个 服务提供器(Service
Provider)的东西,它将这些依赖聚集在了一块,统一申明和管理,让依赖变得更加容易维护。

Service Container和 Service Provider

Laravel 中的使用

定义一个服务提供器:

use IlluminateSupportServiceProvider;

class FooServiceProvider extends ServiceProvider {

    public function register()
    {
        $this->app->bind('foo', function()
        {
            return new Foo;
        });
    }

}

这个代码也不难理解,就是申明一个服务提供器,这个服务提供器有一个 register的方法。这个方法实现了我们上面讲到的依赖注入。

当我们执行下面代码:

App::register('FooServiceProvider');

我们就完成一个注入了。但是这个还是得手动写,所以怎么让 Laravel
自己来做这事儿呢?

我们只要在 app/config/app.php 中的 providers 数组里面增加一行:

'providers' => [
    …
       ‘FooServiceProvider’,
],

这样我们就可以使用 App::make(‘foo’) 来实例化一个类了。

你不禁要问了,这么写也太难看了吧?莫慌,有办法。

Service Container,也就是IOC容器的使用并不依赖Service Provider,例如:

门面模式(Facade)

为了让 Laravel 中的核心类使用起来更加方便,Laravel实现了门面模式。

外觀模式(Facade
pattern),是軟件工程中常用的一種軟件設計模式,它為子系統中的一組接口提供一個統一的高層接口,使得子系統更容易使用。
— 维基百科

$app->make(‘AppModelsPost’);

Laravel 中的使用

我们使用的大部分核心类都是基于门面模式实现的。例如:

$value = Cache::get('key');

这些静态调用实际上调用的并不是静态方法,而是通过 PHP 的魔术方法__callStatic() 讲请求转到了相应的方法上。

那么如何讲我们前面写的服务提供器也这样使用呢?方法很简单,只要这么写:

use IlluminateSupportFacadesFacade;

class Foo extends Facade {

    protected static function getFacadeAccessor() { return ‘foo’; }

}

这样我们就可以通过 Foo::test() 来调用我们之前真正的 FooBar 类的方法了。

这句话和 new AppModels澳门新葡亰网址,Post; 的结果完全一样;
另外你在控制器里使用构造函数,type-hint进行依赖注入,也完全和Service
Provider没有半毛钱关系。

别名(Alias)

有时候我们可能将 Facade 放在我们扩展库中,它有比较深的命名空间,如:LibraryMyClassFoo。这样导致使用起来并不方便。Laravel
可以用别名来替换掉这么长的名字。

我们只要在 app/config/app.php 中 aliases 下增加一行即可:

'aliases' => [
    …
    'Foo' => ‘LibraryMyClassFoo’,
],

这样它的使用就由 LibraryMyClassFoo::test() 变成 Foo::test() 了。

总之,你可以完全不使用Service Provider;

总结

所以有了控制反转(Inversion of
Control)和门面模式(Facade),实际还有服务提供器(Service
Providers)和别名(Alias),我们创建自己的类库和扩展 Laravel
都会方便很多。

这里总结一下创建自己类库的方法:

  1. 在 app/library/MyFoo 下创建类 MyFoo.php
  2. 在 app/library/MyFoo/providers 下创建 MyFooServiceProvider.php
  3. 在 app/library/MyFoo/facades 下创建 MyFooFacade.php
  4. 在 app/config/app.php 中添加 providers 和 aliases

Service Provider 和Contracts

如果说IOC容器的使用并不依赖Service
Provider,那么为什么我们用composer下载扩展包的时候总是要在config/app.php里绑定一下Service
Provider呢,有时候还需要绑定一下Facade;

理解的思路是这样的,Laravel核心类(Services)都是用接口(contracts)+实现来构成的,
如果不理解这个概念,仔细看文档接口那一章。而你在使用的时候,如果要拿到某个接口实现的实例的话,需要用到Service
Container,而要用Service
Container去解析一个接口,而不是直接解析一个类,这时就要用到Service
Provider了,可以说,Service Provider的主要功能,就是来绑定接口的。

下我准备要讲坑爹的事情了,在讲接口绑定前,先了解一些基本的事实:

一些事实

$app->make(‘AppModelsPost’);

你可以这样写,

$app->make(‘post’);

也可以这样写,这里的post是一个别名,这个别名是造成混淆的主要地方;
这个时候你肯定在想,这样写有啥用,我去哪里关联这个别名到AppModelsPost呢?

Service Provider 的 bind方法

对,就是在Service Provider里用bind方法来绑定别名:

$this->app->bind(‘post’, function ($app) {    return new
AppModelsPost;});

这样绑定后你就可以$app->make(‘post’);这样写了;然而搞个别名到目前为止也没什么卵用。没关系,稍后会讲到,它和Facade有关系;我们先来解释文档坑爹的地方:

文档是这样写这个bind方法的:

$this->app->bind(‘HelpSpotAPI’,

function ($app) {  

 return new HelpSpotAPI($app[‘HttpClient’]);}
);

哇擦,您的这第一个参数到底填的啥啊,事实上,第一个参数可以填类的全称,但是如果不是填简称,我这样绑定有任何意义么?
后面再返回一个一样的类实例? 咦?$app[‘HttpClient’]这个是什么??
其实它是告诉你可以在解析类的时候可以再接着注入一个其他类的实例;文档大哥,拜托你解释一下好不好,能不能举个靠谱点的例子…

如果你到其他的扩展包中去看别人的bind的写法,你会发现千奇百怪的绑定写法,先不管他们,现在我们来看Service
Provider对接口的使用方法,最最基本的原理是这样的:

//给一个接口起个别名$this->app->bind(‘event_pusher’, function
($app) {    return new
AppContractsEventPusher;});//指定这个接口应该解析的实例$this->app->bind(‘AppContractsEventPusher’,
‘AppServicesRedisEventPusher’);
通过这两步,我们让这个接口有了别名,也有了解析时对应的实现;

这样,我们就可以:

$app->make(‘event_pusher’);

得到AppServicesRedisEventPusher;

Service Provider 和 Facades

我们来看Facade的写法,比如说IlluminateSupportFacadesCache:

class Cache extends Facade{   

protected static function getFacadeAccessor() { return ‘cache’; }}

这个cache就是上面提到过的别名;

下面我们来看Facade的对应关系图:

Facade Name Facade Class Resolved Class Service Provider Binding Alias
Cache IlluminateSupportFacadesCache IlluminateCacheRepository cache
所以你调用Cache::get(‘user_id’)的时候,实际上是调用了IlluminateSupportFacadesCache
这个类,get并不是这个类的静态方法,事实上,get这个方法在Facade这个类里根本不存在,这正是它设计的本意,当get这个方法不存在的时候,它就会调用Facade基类里的__callStatic魔术方法(需要提前了解这个魔术方法),这个方法中就会把Service
Provider中绑定的类(或接口)解析并返回出来,本例中也就是IlluminateCacheRepository
这个类,所以get其实是IlluminateCacheRepository这个类的方法;

然后我们在再看文档,有的Facade怎么没有别名呢?比如:

Facade Name Facade Class Resolved Class Service Provider Binding Alias
Response IlluminateSupportFacadesResponse 
IlluminateContractsRoutingResponseFactory 

是的,你可以直接写类的全称,而不是别名,如果你看这个IlluminateSupportFacadesResponse源码,它是这样写的:

class Response extends Facade{      
protected static function getFacadeAccessor()    {       

return ‘IlluminateContractsRoutingResponseFactory’;   

}}

可以直接返回该类;

Facade的命名空间到底是什么

我们发现,在使用Cache::get(‘user_id’)的时候,你可以使用use Cache;
也可以使用

use IlluminateSupportFacadesCache;

这是为什么呢?

别忘了,你在config/app.php里面Class Aliases 那里绑定过 Facade
别名,也就是:

 ‘Cache’     => IlluminateSupportFacadesCache::class,

这样绑定过,你就可以直接use Cache来使用Facade了;

补充:

container容器

先来说一下这货是干什么的。现在假设一下你想要做一个开源项目,写一个类实现某种功能。OK,那么再假设,你这个类要做的工作比较复杂,需要

操作数据库
缓存
操作静态文件
操作session
等等……想想都复杂,不过没有关系,以上几个部分显而易见地看出来,它们之间的似乎没什么关联。那么你可以考虑把以上几部分写成独立的类,也就是说针对数据库操作我们写一个DB类封装一些常用操作,针对缓存我们写一个Cache类……
那么好了,当要使用这个类时,可能就会看到这样的代码

<?php
  class SomeClass {

发表评论

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

相关文章

网站地图xml地图