Laravel内核分析-Facades

概述

Facades 是我们在 Laravel 应用开发中使用频率很高的一个组件,叫组件不太合适,其实它们是一组静态类接口或者说代理,

让开发者能简单的访问绑定到服务容器里的各种服务。Laravel 文档中对 Facades 的解释如下:

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

经常用的 Route 就是一个 Facade, 它是 \Illuminate\Support\Facades\Route 类的别名,

这个 Facade 类代理的是注册到服务容器里的 router 服务,所以通过 Route 类我们就能够方便地使用 router 服务中提供的各种服务,而其中涉及到的服务解析完全是隐式地由 Laravel 完成的,这在一定程度上让应用程序代码变的简洁了不少。

 

注册 Facades

说到 Facades 注册又要回到再介绍其它核心组建时提到过很多次的 Bootstrap 阶段了,在让请求通过中间件和路由之前有一个启动应用程序的过程

路径:src/Illuminate/Foundation/Http/Kernel.php@method=handle

 

sendRequestThroughRouter方法

protected function sendRequestThroughRouter($request)
    {
        //将现有实例注册为容器中的共享实例。
        $this->app->instance('request', $request);

        //清除已解析的facade实例。
        Facade::clearResolvedInstance('request');

        //引导HTTP请求的应用程序。
        $this->bootstrap();

        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

bootstrap

public function bootstrap()
    {
        if (! $this->app->hasBeenBootstrapped()) {
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }
protected function bootstrappers()
    {
        return $this->bootstrappers;
    }
  /**
     * 应用程序的引导类。
     *
     * @var string[]
     */
    protected $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,
    ];

在启动应用的过程中 Illuminate\Foundation\Bootstrap\RegisterFacades 这个阶段会注册应用程序里用到的 Facades

 

 

    public function bootstrap(Application $app)
    {
        //清除所有已解决的实例。
        Facade::clearResolvedInstances();

        //设置应用实例。
        Facade::setFacadeApplication($app);

        //获取或创建单例别名加载器实例。
        AliasLoader::getInstance(array_merge(
            $app->make('config')->get('app.aliases', []),
            $app->make(PackageManifest::class)->aliases()
        ))->register();
    }

 

 

在这里会通过 AliasLoader 类的实例将为所有 Facades 注册别名,Facades 和别名的对应关系存放在 config/app.php 文件的 $aliases 数组中

'aliases' => [

    'App' => Illuminate\Support\Facades\App::class,
    'Artisan' => Illuminate\Support\Facades\Artisan::class,
    'Auth' => Illuminate\Support\Facades\Auth::class,
    ......
    'Route' => Illuminate\Support\Facades\Route::class,
    ......
]

看一下 AliasLoader 里是如何注册这些别名的

 

   /**
     * 获取或创建单例别名加载器实例。
     *
     * @param  array  $aliases
     * @return \Illuminate\Foundation\AliasLoader
     */
    public static function getInstance(array $aliases = [])
    {
        if (is_null(static::$instance)) {
            //如果当前实例不存在,直接new 别名
            return static::$instance = new static($aliases);
        }

        //合并已注册的别名
        $aliases = array_merge(static::$instance->getAliases(), $aliases);

        //设置已注册的别名。
        static::$instance->setAliases($aliases);

        return static::$instance;
    }

register

 

 /**
     * 在自动加载器堆栈上注册加载器。
     *
     * @return void
     */
    public function register()
    {
        if (! $this->registered) {
            $this->prependToLoaderStack();

            $this->registered = true;
        }
    }
 /**
     * 将加载方法放在自动加载器堆栈之前。
     *
     * @return void
     */
    protected function prependToLoaderStack()
    {
        spl_autoload_register([$this, 'load'], true, true);
    }

通过上面的代码段可以看到 AliasLoader 将 load 方法注册到了 SPL __autoload 函数队列的头部。看一下 load 方法的源码:

 

 

public function load($alias)
{
    if (isset($this->aliases[$alias])) {
        return class_alias($this->aliases[$alias], $alias);
    }
}

 

不了解 SPL __autoload 是什么 请看#

SPL 中的自动加载器 spl_autoload_register() 可以注册一个或多个自动加载函数,用于在需要时自动加载类和接口。当 PHP 代码尝试使用尚未定义的类或接口时,spl_autoload_register() 将遍历已注册的自动加载函数列表,尝试使用其中一个函数来加载所需的类或接口。
SPL __autoload 是指使用 SPL 中的自动加载器 spl_autoload_register() 来自动加载 PHP 类和接口的过程。__autoload 是早期的自动加载方法,已被 spl_autoload_register() 取代,因为它只能注册一个自动加载函数,而 spl_autoload_register() 可以注册多个函数,并且提供了更多的灵活性和可扩展性

 

在 load 方法里把 $aliases 配置里的 Facade 类创建了对应的别名,比如当我们使用别名类 Route 时 PHP 会通过 AliasLoader 的 load 方法为 Illuminate\Support\Facades\Route 类创建一个别名类 Route,所以我们在程序里使用别 Route 其实使用的就是 Illuminate\Support\Facades\Route 类。

 

解析 Facade 代理的服务

把 Facades 注册到框架后我们在应用程序里就能使用其中的 Facade 了,

比如注册路由时我们经常用 Route::get('/uri', 'Controller@action);,那么 Route 是怎么代理到路由服务的呢,这就涉及到在 Facade 里服务的隐式解析了, 我们看一下 Route 类的源码:

class Route extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'router';
    }
}

只有简单的一个方法,并没有 getpostdelete 等那些路由方法,父类里也没有,不过我们知道调用类不存在的静态方法时会触发 PHP 的__callStatic 静态方法

public static function __callStatic($method, $args)
{
    $instance = static::getFacadeRoot();

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

    return $instance->$method(...$args);
}

//获取Facade根对象
public static function getFacadeRoot()
{
    return static::resolveFacadeInstance(static::getFacadeAccessor());
}

/**
 * 从服务容器里解析出Facade对应的服务
 */
protected static function resolveFacadeInstance($name)
{
    if (is_object($name)) {
        return $name;
    }

    if (isset(static::$resolvedInstance[$name])) {
        return static::$resolvedInstance[$name];
    }

    return static::$resolvedInstance[$name] = static::$app[$name];
}

通过在子类 Route Facade 里设置的 accessor (字符串 router), 从服务容器中解析出对应的服务,

router 服务是在应用程序初始化时的 registerBaseServiceProviders 阶段(具体可以看 Application 的构造方法)被 \Illuminate\Routing\RoutingServiceProvider 注册到服务容器里的:

class RoutingServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->registerRouter();
        ......
    }

    /**
     * Register the router instance.
     *
     * @return void
     */
    protected function registerRouter()
    {
        $this->app->singleton('router', function ($app) {
            return new Router($app['events'], $app);
        });
    }
    ......
}

router 服务对应的类就是 \Illuminate\Routing\Router, 所以 Route Facade 实际上代理的就是这个类,Route::get 实际上调用的是 \Illuminate\Routing\Router 对象的 get 方法

    /**
     * 向路由器注册新的GET路由。
     *
     * @param  string  $uri
     * @param  array|string|callable|null  $action
     * @return \Illuminate\Routing\Route
     */
    public function get($uri, $action = null)
    {
        //将路由添加到基础路由集合。
        return $this->addRoute(['GET', 'HEAD'], $uri, $action);
    }

 

ps:

  1. 解析服务时用的 static::$app 是在最开始的 RegisterFacades 里设置的,它引用的是服务容器。
  2. static::$app ['router']; 以数组访问的形式能够从服务容器解析出 router 服务是因为服务容器实现了 SPL 的 ArrayAccess 接口,对这个没有概念的可以看下官方文档 ArrayAccess

 

总结

通过梳理 Facade 的注册和使用流程我们可以看到 Facade 和服务提供器(ServiceProvider)是紧密配合的,所以如果以后自己写 Laravel 自定义服务时除了通过组件的 ServiceProvider 将服务注册进服务容器,还可以在组件中提供一个 Facade 让应用程序能够方便的访问你写的自定义服务。

版权声明:
作者:linrux
链接:https://www.tot7.cn/technology/laravel/451.html
来源:Code林
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>
文章目录
关闭
目 录