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

简述

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

基本概念

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

Service Container和 Service Provider

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

$app->make('App\Models\Post');

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

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

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('App\Models\Post');

你可以这样写,

$app->make('post');

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

Service Provider 的 bind方法

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

$this->app->bind('post', function ($app) {    return new App\Models\Post;});

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

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

$this->app->bind('HelpSpot\API',

function ($app) {  

 return new HelpSpot\API($app['HttpClient']);}
);

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

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

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

这样,我们就可以:

$app->make('event_pusher');

得到App\Services\RedisEventPusher;

Service Provider 和 Facades

我们来看Facade的写法,比如说Illuminate\Support\Facades\Cache:

class Cache extends Facade{   

protected static function getFacadeAccessor() { return 'cache'; }}

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

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

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

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

Facade Name Facade Class Resolved Class Service Provider Binding Alias
Response Illuminate\Support\Facades\Response 
Illuminate\Contracts\Routing\ResponseFactory 

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

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

return 'Illuminate\Contracts\Routing\ResponseFactory';   

}}

可以直接返回该类;

Facade的命名空间到底是什么

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

use Illuminate\Support\Facades\Cache;

这是为什么呢?

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

 'Cache'     => Illuminate\Support\Facades\Cache::class,

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

补充:

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()是不是就全部切换成

时间: 2024-10-24 03:21:48

Laravel的Container/ServiceProvider/Facade之间的关系的相关文章

slf4j-api、slf4j-log4j12以及log4j之间的关系

几乎在每个jar包里都可以看到log4j的身影,在多个子工程构成项目中,slf4j相关的冲突时不时就跳出来让你不爽,那么slf4j-api.slf4j-log4j12还有log4j是什么关系?      slf4j:Simple Logging Facade for Java,为java提供的简单日志Facade.Facade门面,更底层一点说就是接口.它允许用户以自己的喜好,在工程中通过slf4j接入不同的日志系统.更直观一点,slf4j是个数据线,一端嵌入程序,另一端链接日志系统,从而实现将

总结java实现八大排序算法及之间的关系

8种排序之间的关系: 1, 直接插入排序 (1)基本思想:在要排序的一组数中,假设前面(n-1)[n>=2] 个数已经是排 好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数 也是排好顺序的.如此反复循环,直到全部排好顺序. (2)实例 (3)用java实现 [java] view plaincopy package com.njue; public class insertSort { public insertSort(){ inta[]={49,38,65,97,76,13,27,

UIView的alpha、hidden和opaque属性之间的关系和区别[转]

UIView的alpha.hidden和opaque属性之间的关系和区别 作者:wangzz 原文地址:http://blog.csdn.net/wzzvictory/article/details/10076323 转载请注明出处   UIView的这几个属性让我困惑了好一阵子,通过翻看官方文档和stackoverflow等网上资源,对它们有了一定理解,现分享出来.如理解有误,还请大家指出.   一.alpha 液晶显示器是由一个个的像素点组成的,每个像素点都可以显示一个由RGBA颜色空间组成

XML和数据库之间的关系

xml|数据|数据库  1.0 简介    本论文简要的探讨了XML和数据库之间的关系,同时列出一些可以使用数据库处理XML文档的软件. 虽然这里不打算详尽地介绍这些软件,但是笔者希望它能够描述使用数据库处理XML文档中的主要部分.这里有点偏向与关系数据库,因为我的经验如此.     2.0 XML是数据库吗?     在开始讨论XML和数据库之前,我们需要回答一个萦绕在很多心头的问题: "XML是数据库吗?"在严格意义上将,如果"XML"是指XML文档时,答案是&

构架、框架、设计模式之间的关系简述

设计 一.软件体系结构和框架的定义 软件体系结构的英文单词是"architecture". Architecture的基本词义是建筑.建筑学.建筑风格. 软件体系结构虽然根植于软件工程,但还处于一个研究发展的阶段,迄今为止还没有一个为大家所公认的定义. <设计模式>中对框架的定义是框架就是一组相互协作的类,对于特定的一类软件,框架构成了一种可重用的设计. 软件框架是项目软件开发过程中提取特定领域软件的共性部分形成的体系结构,不同领域的软件项目有着不同的框架类型.框架的作用在

中文Access2000速成教程--1.8 定义表之间的关系

access|教程|速成|中文 第八讲 定义表之间的关系 前面定义了主键,以及相应的表后,就可以制定各表部的关系,从而建立起一个关系数据库.的应用与管理关系数据库,中文Access 2000具有很强的能力,这正是中文Visual FoxPro 6与中文Excel 2000所不及的.为了建立关系数据库,首先要建立好各相关表,接着将它们全部关闭,即可按下列步骤进行操作. 注意:中文Access 2000不能在已打开的表之间创建或修改关系,所以要关闭所有打开的表,这与中文Visual FoxPro 6

浅谈网站优化之新访客和老访客之间的关系

我们都知道现在大部分网站我们都依靠搜索引擎,我们不断的优化.不断的提高访问量,当我们网站IP逐步增高的同时我们沉浸在胜利的喜悦中,可我们却忽略新访客增加的数据和老访客增加的数据不成比例!这就要我们思考的问题了,所以做为一个站长我们对数据的分析不能忽视!下面我就来简单探讨下网站新访客和老访客之间的关系!   (上图为网站A 数据来源:百度统计 截图后为方便大家看做成了一张图片)   (上图为网站B 数据来源:百度统计 截图后为方便大家看做成了一张图片) 上面2个图片,分别为网站A 网站B,在这里笔

探讨网站权重与关键字权重之间的关系

探讨:网站权重与关键字权重之间的关系,我们知道对于一个网站而言,其关键字可以是几个,十几个,也或者是几十个,甚至上百个,网站的规模似乎也就决定了网站关键字的数量多少,一般对于一个企业站而言,其会有3~5个目标关键词,除了这些目标关键词之外,剩下的就是一定数量的长尾关键词. 下面我们来看看关键字排名是如何进行的:一般网站相关优化流程是,先选择目标关键词来做,对一部分目标关键词进行站内文章,站外外链等,通常需要做哪一个关键词就会对某一个关键词进行优化,从这种意义上来讲,其目的就是提高此关键词在整个网

敏捷与结构性模块化(一) 探讨结构性模块化和敏捷之间的关系

1 简介 敏捷开发方法论日益流行,然而大多数"敏捷"专家和分析师都在孤立地讨论敏捷,也就是说忽视了系统"结构"(Kirk Knoernschild是一个例外,他编写了一本名为<Java Application Architecture>的图书阐述这一理念).考虑到"敏捷"是基础实体的一个重要特性或属性,那么,这种疏忽令人感到很惊讶.一个实体要具有"敏捷"的特性,它必须具有高度的结构性模块化(structural m