欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

Laravel 架构中的 Container/ServiceProvider/Facade 有大用

最近有空继续学习laravel,对laravel文档中系统架构部分基本的有一些了解,和大家分享一下。

container容器#

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

  • 操作数据库
  • 缓存
  • 操作静态文件
  • 操作session

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

<?php
  class SomeClass {

    public function dbTask() {
      $db = new DBClass(['localhost',3306,root,pwd]);
      //数据库操作
    }

    public function cacheTask() {
      $cache = new Cache(['localhost',11211]);
      //缓存操作
    }
    //...
  }

看起来其实还好,但是想想,如果SomeClass依赖很多外部的类,我们每次使用SomeClass都必须先use进那个类,再通过参数实例化那个对象,这样非常麻烦。那么,这时候就产生了容器的概念。
什么叫容器呢?简单讲,就是把我们的应用可能用到的service(像上述那些类,因为专职某个功能,称它们为service),全部绑定到一个全局的对象里面,这个对象就叫容器。可能就像下面这样

class Container {

  protected $service_arr = [];

  public function bind($name, $instance) {

     $this->servie_arr[$name] = $instance;

  }

  public function get($name) {

    return $this->service_arr[$name];

  }

}

那么我们以后要用到某些service的时候,就可以直接通过Container->get('some')的方式来获取实例了比如

<?php
  class SomeClass {

    priavte $container;

    public function construct($con) {

       $this->container = $con;

    }

    public function dbTask() {

      $db = $this->container->get('db');
      //数据库操作
    }

    public function cacheTask() {

      $cache = $this->container->get('cache');
      //缓存操作
    }
    //...
  }

OK,现在你大概明白container到底是干嘛的了吧。再简单一点讲,就是把我们可能需要在代码内部手动实例化的对象,全部绑定到container这个对象上,以后要用就来这儿取。至于它的好处我就不赘述了,可以看看设计模式关于IoC和DI的章节(控制反转和依赖注入)

ServiceProvider#

理解了上面的container,你就会有疑问

如果要把所有可能的service绑定,container类是否会变得非常庞大?

是的,如果你在应用的开始调用N次bind方法,依次绑定需要的所有service,这个文件将难以维护,比如

class Container {

  protected $service_arr = [];

  public function bind($name, $instance) {

     $this->servie_arr[$name] = $instance;

  }

  public function get($name) {

    return $this->service_arr[$name];

  }

}

$con = new Container();

$db = new DBClass(['locahost',3306,root,pwd]);

$con->bind('db', $db);

$cache = new Cache(['127.0.0.1',11211]);

$con->bind('cache',$cache);

//...

所有的类的实例化都写到一个文件里了,讲会是耦合性升高。
ServiceProvider就是解决这个问题的
每一个需要绑定到container的service,你需要创建一个对应ServiceProvider,这个ServiceProvider中有一个register方法,在这里面进行绑定,而不是像上述那样。比如:

class SomeServiceProvider extends ServiceProvider {

  public function register() {

      $this->app->bind('some', new Some());

  }

}

Laravel的container为了知道需要绑定哪些service,它会去读一个数组,这个数组是config/app.php中的Providers,然后你只需要把你的SomeServiceProvider写进这个数组,就可以绑定上了。

Facade#

刚开始看Facade其实还是不太好理解,到底什么是Facade呢,它存在的意义又是什么呢?
我的理解

  • 简化对service的使用,可以理解为语法糖
  • 可以方便的替换

可能你有疑惑,到底替换什么呢?看看下面的代码吧

use Illuminate\Support\Facades\Redis;

class IndexController extends Controller {

  public function index() {

    $redis = Redis::connect();

    //do something

  }

}

然而当我们打开Illuminate\Support\Facades\Redis看看

file

并没有connect的静态方法啊?但是可以发现唯一的一个方法返回了一个redis字符串,有什么玄机呢
那么我们再看看RedisServiceProvider

file
我们的RedisServiceProvider在container里绑定了一个redis……所以大概你也猜到了,没错!

Redis::connect() 等价于 $this->app->get('redis')->connect()

get的到底是什么,取决于Facade中getFacadeAccessor方法返回的字符串!
这样有什么好处呢?文档也说了,除了简便使用以外最大的用处是测试,想想看,你把Redis的getFacadeAccessor方法返回值变成'memcached',那么你所有使用Redis::some()是不是就全部切换成memcache啦?

 本帖已被设为精华帖!
 
回复数量: 12
  • lifesign Learn From Life
     ⋅ 2年前

    很棒的文章!

  • stoensin
    stoensin
     ⋅ 2年前

    :smile: 清晰多了

  • caoglish
    caoglish
     ⋅ 2年前

    Facade其实就是singleton的一个变种,使用static方法和singleton的缺点是难以进行单元测试(无法mock)。

    所以这里有个问题,使用Facade的方法如何测试?

    我觉得Laravel还是有解决方案的,在配置文档中Facade和对应的库是配置的,只要在测试过程中把Mapping的库给替换成mock的就可以,不过似乎就是没有相应的文档。

  • caibirdme
    caibirdme
     ⋅ 2年前

    @caoglish laravel上单元测试我没做过,但我觉得因为都是通过contracts来实现的interface,所以我觉得替换起来很方便

  • 湖爷洞
    湖爷洞
     ⋅ 2年前

    Facade 的中文意思本身就是门面... 面门的主要作用嘛...就是要看起来舒服嘛...哈哈哈 :smile:

  • maskwang
    maskwang
     ⋅ 2年前

    讲解很赞

  • laravel
    laravel
     ⋅ 2年前

    Facades是一种设计模式的实现,让很多类可以直接方便的被调用。

  • wedojava
    wedojava
     ⋅ 2年前

    @湖爷洞 啊,让我想起了多年前学Java的时候的JSF,后来用c#开发的时候还自己写门面,你一说门面我一下就想起来了.

  • 湖爷洞
    湖爷洞
     ⋅ 2年前
  • qloog
    qloog
     ⋅ 2年前

    貌似又明白了一些!

  • sphenginx
    sphenginx
     ⋅ 1年前

    学习了,谢谢楼主分享 :thumbsup: :smile:

  • tyua07
     ⋅ 1年前

    @caoglish 你好你现在的这样跑起来单元测试了么?我最近也在了解这种方式的单元测试

 来自  https://laravel-china.org/topics/769/containerserviceproviderfacade-in-laravel-architecture
普通分类: