Facade

作者: __missing | 来源:发表于2019-03-05 18:11 被阅读0次

    前言

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

    相关文章

      网友评论

          本文标题:Facade

          本文链接:https://www.haomeiwen.com/subject/zkyouqtx.html