Facade

前言

使用过laravel的同学对于DB::table(‘XXX’)这样的写法一定不陌生吧,但是最开始这样写有没有感觉怪怪的呢,DB类里根本没有我们调用的table方法?更奇怪的是不用use DB,编辑器提示DB不存在,但是程序运行起来依然没有问题,接下来我们就看看这个简单的写法是如何实现的。

概念

我看好多地方都把Facade叫做门面有点不太能懂,个人更喜欢把Facade理解为服务的代理,他其实就是做了简单一个转发。比如访问DB,其实就是Facade/DB将这个请求转发给$app['db']或者DatabaseManager这个类。下边是laravel文档中的解释

Facades 为应用程序的 服务容器 中可用的类提供了一个「静态」接口。Laravel 本身附带许多的 facades,甚至你可能在不知情的状况下已经在使用他们!Laravel 「facades」作为在服务容器内基类的「静态代理」,拥有简洁、易表达的语法优点,同时维持着比传统静态方法更高的可测试性和灵活性。

实现

db的注册

数据库服务提供者DatabaseServiceProvider的register中注册了db,供Facade/DB引导时使用。这里的注册就不详细说了,可以参考Laravel核心代码学习 -- 服务提供器

class DatabaseServiceProvider extends ServiceProvider
{
  public function register()
    {
        Model::clearBootedModels();

        $this->registerConnectionServices();

           }

  protected function registerConnectionServices()
    {
         $this->app->singleton('db.factory', function ($app) {
            return new ConnectionFactory($app);
        });

        $this->app->singleton('db', function ($app) {
            return new DatabaseManager($app, $app['db.factory']);  //这里注册了db
        });

        $this->app->bind('db.connection', function ($app) {
            return $app['db']->connection();
        });
    }
}

Facade的别名

先来看看在不使用use DB的情况下,程序是如何找到Facade/DB这个类的。这里的Facade的别名处理就是发生引导时触发的。注意这里如果是在一个类中使用DB在不使用use时一定要用\DB::table('XXX'),因为如果不添加\默认是加载当前类的命名空间下的DB。

// index.php
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle( //处理请求
    $request = Illuminate\Http\Request::capture()
);

$response->send();

$kernel->terminate($request, $response);

在kernel的handle中进行了引导

  public function handle($request)
    {
        
        $this->sendRequestThroughRouter($request);  //通过路由进行相应的处理

        $this->app['events']->dispatch(
            new Events\RequestHandled($request, $response)
        );

        return $response;
    }

    protected function sendRequestThroughRouter($request)
    {
        $this->app->instance('request', $request);

        Facade::clearResolvedInstance('request');

        $this->bootstrap(); //在处理路由逻辑时先完成程序的引导

        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    } 
    public function bootstrap()
    {
        if (! $this->app->hasBeenBootstrapped()) {
           $bootstrappers = [
          \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
          \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
          \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
          \Illuminate\Foundation\Bootstrap\RegisterFacades::class,  //这个是我们这里要说的
          \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
          \Illuminate\Foundation\Bootstrap\BootProviders::class,
    ];
            $this->app->bootstrapWith($bootstrappers());
        }
    }
public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {
            $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);

            $this->make($bootstrapper)->bootstrap($this);

            $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
        }
    }
// \Illuminate\Foundation\Bootstrap\RegisterFacades::class
public function bootstrap(Application $app)
    {
        Facade::clearResolvedInstances();

        Facade::setFacadeApplication($app);

        AliasLoader::getInstance(array_merge(
            $app->make('config')->get('app.aliases', []), //获取config/app中aliases数组
            $app->make(PackageManifest::class)->aliases()
        ))->register();
    }
  public function register()
    {
        if (! $this->registered) {
            $this->prependToLoaderStack();

            $this->registered = true;
        }
    }

protected function prependToLoaderStack()
    {
        spl_autoload_register([$this, 'load'], true, true); //自动加载机制,当访问一个不存在的的类时就在这里找别名
    }

public function load($alias)
    {
        if (isset($this->aliases[$alias])) { //这里就解决了即使不use DB,依然可以访问到Facade/DB
            return class_alias($this->aliases[$alias], $alias); 
        }
    }

方法的调用

我们在看看这个不存在的table是如何执行的,所有的Facade都必须继承abstract class Facade这个抽象类并且重写getFacadeAccessor这个方法,抽象类中有__callStatic这个抽象方法。当调用一个不存在的静态方法时,则会触发该方法。

  class DB extends Facade
{
    protected static function getFacadeAccessor() //重写父类的方法,返回字符串,这个字符串其实就是这个Facade代理的服务在容器中注册时用的abstruct
    {
        return 'db';
    }
}

abstract class Facade
{
  public static function __callStatic($method, $args) //当调用一个不存在的静态方法时,则会触发该方法
    {
        $instance = static::getFacadeRoot(); //一个实例,DatabaseManager

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        return $instance->$method(...$args); //调用DatabaseManager这个类中的table方法。
    }

  public static function getFacadeRoot()
    {
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }


  protected static function resolveFacadeInstance($name)
    {
        return staic::$app[$name]; //返回服务容器的$app['db']
    }

}

最后

说了这么多其实DB::table('XXX'),就等于$app->make('db')->table('XXX')。Facade和服务提供者之前关系紧密,学习时两者要结合看,本来打算写一篇关于服务提供者的文章,结果发现自己对他理解有限,就暂时放弃了以后有机会再写。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,039评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,426评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,417评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,868评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,892评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,692评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,416评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,326评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,782评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,957评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,102评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,790评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,442评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,996评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,113评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,332评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,044评论 2 355

推荐阅读更多精彩内容