Facade

2019-03-05  本文已影响0人  __missing

前言

使用过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和服务提供者之前关系紧密,学习时两者要结合看,本来打算写一篇关于服务提供者的文章,结果发现自己对他理解有限,就暂时放弃了以后有机会再写。

上一篇下一篇

猜你喜欢

热点阅读