当前位置: 首页 > news >正文

Laravel核心解读--Facades

什么是Facades

Facades是我们在Laravel应用开发中使用频率很高的一个组件,叫组件不太合适,其实它们是一组静态类接口或者说代理,让开发者能简单的访问绑定到服务容器里的各种服务。Laravel文档中对Facades的解释如下:

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

我们经常用的Route就是一个Facade, 它是\Illuminate\Support\Facades\Route类的别名,这个Facade类代理的是注册到服务容器里的router服务,所以通过Route类我们就能够方便地使用router服务中提供的各种服务,而其中涉及到的服务解析完全是隐式地由Laravel完成的,这在一定程度上让应用程序代码变的简洁了不少。下面我们会大概看一下Facades从被注册进Laravel框架到被应用程序使用这中间的流程。Facades是和ServiceProvider紧密配合的所以如果你了解了中间的这些流程对开发自定义Laravel组件会很有帮助。

注册Facades

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

//Class: \Illuminate\Foundation\Http\Kernel
 
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());
}

//引导启动Laravel应用程序
public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
        /**依次执行$bootstrappers中每一个bootstrapper的bootstrap()函数
         $bootstrappers = [
               'Illuminate\Foundation\Bootstrap\DetectEnvironment',
             'Illuminate\Foundation\Bootstrap\LoadConfiguration',
              'Illuminate\Foundation\Bootstrap\ConfigureLogging',
             'Illuminate\Foundation\Bootstrap\HandleExceptions',
             'Illuminate\Foundation\Bootstrap\RegisterFacades',
             'Illuminate\Foundation\Bootstrap\RegisterProviders',
             'Illuminate\Foundation\Bootstrap\BootProviders',
            ];*/
            $this->app->bootstrapWith($this->bootstrappers());
    }
}

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

class RegisterFacades
{
    /**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    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里是如何注册这些别名的

// class: Illuminate\Foundation\AliasLoader
public static function getInstance(array $aliases = [])
{
    if (is_null(static::$instance)) {
        return static::$instance = new static($aliases);
    }

    $aliases = array_merge(static::$instance->getAliases(), $aliases);

    static::$instance->setAliases($aliases);

    return static::$instance;
}

public function register()
{
    if (! $this->registered) {
        $this->prependToLoaderStack();

        $this->registered = true;
    }
}

protected function prependToLoaderStack()
{
    // 把AliasLoader::load()放入自动加载函数队列中,并置于队列头部
    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);
    }
}

在load方法里$aliases配置里的Facade类创建了对应的别名,比如当我们使用别名类Route时PHP会通过AliasLoader的load方法为把Illuminate\Support\Facades\Route::class类创建一个别名类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';
    }
}

只有简单的一个方法,并没有get, post, delete等那些路由方法, 父类里也没有,不过我们知道调用类不存在的静态方法时会触发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方法

/**
 * Register a new GET route with the router.
 *
 * @param  string  $uri
 * @param  \Closure|array|string|null  $action
 * @return \Illuminate\Routing\Route
 */
public function get($uri, $action = null)
{
    return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}

补充两点:

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

总结

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

本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。

相关文章:

  • 学习rsyslog总结
  • 中国电信携手 Orange Business Services 扩大战略合作 为企业提供无缝物联网体验
  • add_argument 图片路径
  • 组件化、模块化、集中式、分布式、服务化、面向服务的架构、微服务架构概念介绍...
  • spring @transactional 注解使用注意点
  • Python模块-time模块
  • Java并发编程(二)优势和风险
  • 聚焦2017中国网络安全年会
  • 电路中IC器件电压符号的解释
  • XVIII Open Cup named after E.V. Pankratiev. GP of Romania
  • 4199. [NOI2015]品酒大会【后缀数组+并查集】
  • 瞄准MSP市场风口,Bespin为企业转型保驾护航
  • 自己动手写CPU——寄存器堆、数据存储器(基于FPGA与Verilog)
  • OO1-3总结
  • nodejs使用log4js记录日志
  • 【mysql】环境安装、服务启动、密码设置
  • js算法-归并排序(merge_sort)
  • Nginx 通过 Lua + Redis 实现动态封禁 IP
  • node学习系列之简单文件上传
  • PHP 使用 Swoole - TaskWorker 实现异步操作 Mysql
  • php面试题 汇集2
  • php中curl和soap方式请求服务超时问题
  • python_bomb----数据类型总结
  • Redis 懒删除(lazy free)简史
  • Traffic-Sign Detection and Classification in the Wild 论文笔记
  • 包装类对象
  • 力扣(LeetCode)21
  • 如何用vue打造一个移动端音乐播放器
  • 中国人寿如何基于容器搭建金融PaaS云平台
  • const的用法,特别是用在函数前面与后面的区别
  • 如何正确理解,内页权重高于首页?
  • ​linux启动进程的方式
  • ​Python 3 新特性:类型注解
  • #调用传感器数据_Flink使用函数之监控传感器温度上升提醒
  • (10)工业界推荐系统-小红书推荐场景及内部实践【排序模型的特征】
  • (13)Hive调优——动态分区导致的小文件问题
  • (NO.00004)iOS实现打砖块游戏(十二):伸缩自如,我是如意金箍棒(上)!
  • (zhuan) 一些RL的文献(及笔记)
  • (附源码)spring boot校园健康监测管理系统 毕业设计 151047
  • (利用IDEA+Maven)定制属于自己的jar包
  • (六)激光线扫描-三维重建
  • (三)Pytorch快速搭建卷积神经网络模型实现手写数字识别(代码+详细注解)
  • (转) Android中ViewStub组件使用
  • (转)visual stdio 书签功能介绍
  • (转载)虚幻引擎3--【UnrealScript教程】章节一:20.location和rotation
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .NET 的程序集加载上下文
  • .NET牛人应该知道些什么(2):中级.NET开发人员
  • .secret勒索病毒数据恢复|金蝶、用友、管家婆、OA、速达、ERP等软件数据库恢复
  • @AutoConfigurationPackage的使用
  • [2016.7 test.5] T1
  • [20161214]如何确定dbid.txt
  • [20170705]diff比较执行结果的内容.txt
  • [C puzzle book] types
  • [C/C++]数据结构 循环队列