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\Kernelprotected 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类创建一个别名类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源码学习里,欢迎访问阅读。

Laravel核心代码学习 -- Facades相关推荐

  1. Laravel核心代码学习--用户认证系统(基础介绍)

    用户认证系统(基础介绍) 使用过Laravel的开发者都知道,Laravel自带了一个认证系统来提供基本的用户注册.登录.认证.找回密码,如果Auth系统里提供的基础功能不满足需求还可以很方便的在这些 ...

  2. Laravel核心代码学习--用户认证系统的实现细节

    用户认证系统的实现细节 上一节我们介绍了Laravel Auth系统的基础知识,说了他的核心组件都有哪些构成,这一节我们会专注Laravel Auth系统的实现细节,主要关注Auth也就是AuthMa ...

  3. Laravel核心解读--Facades

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

  4. 莫队算法(普通莫队、带修莫队、树上莫队、不删除莫队)学习笔记【理解+套路/核心代码+例题及题解】

    一.理解 我的理解就是巧妙的暴力,利用双指针以及分块思想,巧妙的移动双指针,时间复杂度可以达到O(NlogN). 强推博客:写的又好又全.链接 二.套路 1.普通莫队 [1]核心代码 bool cmp ...

  5. Unity学习日志_七行核心代码实现第三人称游戏的相机逻辑

    七行核心代码实现第三人称游戏的相机逻辑: 使用到的一些知识: 欧拉角 虚拟轴 四元数计算 向量计算 代码实现: 其中RoundMovement方法为核心方法(): using UnityEngine; ...

  6. Laravel核心解读--完结篇

    过去一年时间写了20多篇文章来探讨了我认为的Larave框架最核心部分的设计思路.代码实现.通过更新文章自己在软件设计.文字表达方面都有所提高,在刚开始决定写Laravel源码分析地文章的时候我地期望 ...

  7. Laravel核心解读--完结篇 1

    过去一年时间写了20多篇文章来探讨了我认为的Larave框架最核心部分的设计思路.代码实现.通过更新文章自己在软件设计.文字表达方面都有所提高,在刚开始决定写Laravel源码分析地文章的时候我地期望 ...

  8. Laravel源码学习文章汇总

    过去一年时间写了20多篇文章来探讨了我认为的Larave框架最核心部分的设计思路.代码实现.通过更新文章自己在软件设计.文字表达方面都有所提高,在刚开始决定写Laravel源码分析地文章的时候我地期望 ...

  9. Laravel核心解读--服务提供器(ServiceProvider)

    服务提供器是所有 Laravel 应用程序引导中心.你的应用程序自定义的服务.第三方资源包提供的服务以及 Laravel 的所有核心服务都是通过服务提供器进行注册(register)和引导(boot) ...

最新文章

  1. MIME types [记录]
  2. 转 layer的Icon样式以及一些常用的layer窗口使用
  3. 2.2.2 操作系统之进程调度的时机(主动放弃与被动放弃)、切换与过程(广义与狭义)、方式(非剥夺与剥夺)
  4. uniapp 微信小程序打包 vendor.js过大 导致打包超过2M
  5. 听障学生计算机课本,面向听障学生程序设计的计算机教学辅助系统
  6. WinDbg 命令三部曲:(二)WinDbg SOS 扩展命令手
  7. 插入始终是1_插入式电磁流量计的安装说明
  8. POJ2155 Matrix
  9. 对侯捷《Word 排版艺术》的期待
  10. tesseract ocr 5.0 Api调用,delphi源码实现--识别率超高速度快
  11. Android:这是一份全面 详细的Webview使用攻略
  12. 什么是CGCS2000坐标系
  13. WIPE与Format的种种神马情况
  14. 2021深圳观澜中学高考成绩查询入口,2019深圳龙华中学、观澜中学、龙华高级中学录取分数线及高考成绩喜报...
  15. 腾讯云Ubuntu18.04配置OpenPCDet深度学习环境
  16. 官方对于,Mozilla公司开发的FireFox浏览器,评价有多好!
  17. Word编辑公式 下划线取消自动套用格式 下划线不转义
  18. 看门狗的原理和驱动实现
  19. 应用 | 小O地图在通勤路线规划的应用
  20. BlackNurse新型DoS攻击 15M流量就可以打瘫思科防火墙 思科做出了回应

热门文章

  1. android canvas绘制圆角_Android自定义View撸一个渐变的温度指示器(TmepView)
  2. axios ajax和flask传输json数据
  3. javascript 之 push
  4. python 播放 wav 文件
  5. Java源码详解五:ArrayList源码分析--openjdk java 11源码
  6. C++的类什么时候需要虚析构函数
  7. Java Arrays.Sort方法重写
  8. linux查看当前的工作目录,Linux-查看当前工作目录(pwd)
  9. VS2019遇到的坑——C4716
  10. vector 容器 动态数组总结