Laravel Facade 实现原理揭秘

    xiaoxiao2021-08-23  107

    在使用Laravel 框架的时候会看到很多 Cache::get() 这样的用法,称之为 Facade,门面。

    但是代码中即没有看到使用 Cache 相关的命名空间,且在 Composer 自动加载中也没有相关的自动加载规则。那这是如何实现的呢?让我们从框架源码去发现。

    Laravel 的入口文件是 public/index.php,此文件载入了 autoload.php, app.php 2个文件:

    require __DIR__.'/../bootstrap/autoload.php'; $app = require_once __DIR__.'/../bootstrap/app.php';

    顾名思义 autoload.php 实现了自动加载,app.php 和容器相关。

    初始化容器的过程这里不详细解说,不是本文重点。

    ==============================重点来了================================

    初始化容器后,执行了以下代码:

    // 得到 App\Http\Kernel 实例对象 $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); // 执行对象handle 方法,此方法继承自 Illuminate\Foundation\Http\Kernel $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ); 让我们去看下 handle() 做了些什么:

    public function handle($request) { //......省略...... $response = $this->sendRequestThroughRouter($request); //......省略...... }

    sendRequestThroughRouter 方法:

    protected function sendRequestThroughRouter($request) { //......省略...... // 启动一些启动器,诸如异常处理,配置,日志,Facade,运行环境监测等 $this->bootstrap(); //......省略...... }

    bootstrap 方法:

    public function bootstrap() { if (! $this->app->hasBeenBootstrapped()) {   $this->app->bootstrapWith($this->bootstrappers()); } }

    $this->bootstrappers() 中返回 $this->bootstrappers 保存的数据:

    protected $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', ];

    可以看到 :

    'Illuminate\Foundation\Bootstrap\RegisterFacades',

    此类就是实现 Facade 的一部分,bootstrap 方法中 $this->app->bootstrapWith 会调用类的 bootstrap 方法:

    class RegisterFacades { public function bootstrap(Application $app) { //......省略...... AliasLoader::getInstance($app->make('config')->get('app.aliases'))->register(); } }

    $app->make('config')->get('app.aliases') 返回的是 config/app.php 配置文件中 'aliases' 键对应的值,

    我们继续往下看 AliasLoader::getInstance 方法:

    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; } 默认情况下 static::$instance 是 null,所以我们会得到 new static($aliases)。

    new static 是一个自 5.3以后的新特性,参见:https://secure.php.net/manual/zh/language.oop5.late-static-bindings.php

    回头再看 

    AliasLoader::getInstance($app->make('config')->get('app.aliases'))->register();

    中调用了 AliasLoader->register 方法:

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

    prependToLoaderStack 方法:

    这里注册了当前对象中 load 方法为自动加载函数

    protected function prependToLoaderStack() { spl_autoload_register([$this, 'load'], true, true); }

    load 方法:

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

    这里的 $this->aliases 即是 AliasLoader:getInstance 中实例化一个对象: new static($aliases)  时构造函数中设置的:

    private function __construct($aliases) { $this->aliases = $aliases; }

    这里 class_alias 是实现 Facade 的核心要点之一,该函数原型:

    bool class_alias ( string $original, string $alias[, bool $autoload = TRUE ] ) 第三个参数默认为 true,意味着如果原始类(string $original)没有加载,则自动加载。

    更多该函数的解释请自行翻阅手册。

    现在可以解答为什么 Cache 没有使用命名空间,composer 没有设置自动加载的情况可以立即使用了:

    自动加载函数会尝试加载 Cache 这个别名对应的实际类所在的文件(加载这个类文件时会去调用SPL自动加载函数队列中其他函数,比如 composer 自动加载函数,并遵从规则)

    接下来说一下 Cache::get() 中为什么可以像静态方法一样调用任何类的方法(哪怕不是静态方法)。

    找到并打开 Cache 类文件:vendor/laravel/framework/src/Illuminate/Support/Facades/Cache.php

    文件内容是这样的:

    namespace Illuminate\Support\Facades; /** * @see \Illuminate\Cache\CacheManager * @see \Illuminate\Cache\Repository */ class Cache extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return 'cache'; } }

    看一下 父类 Illuminate\Support\Facades,发现父类中实现了魔术方法 __callStatic:

    public static function __callStatic($method, $args) { $instance = static ::getFacadeRoot(); if (!$instance) { throw new RuntimeException('A facade root has not been set.'); } switch (count($args)) { case 0: return $instance->$method(); case 1: return $instance->$method($args[0]); case 2: return $instance->$method($args[0], $args[1]); case 3: return $instance->$method($args[0], $args[1], $args[2]); case 4: return $instance->$method($args[0], $args[1], $args[2], $args[3]); default: return call_user_func_array([$instance, $method], $args); } }

    谜底揭开了,原来是通过魔术方法去实现的。

    简单的写了一段代码,用于实现类似 Facade 的调用方式:

    namespace Illuminate\Support\Facades { class Facades { public function __call($name, $params) { return call_user_func_array([$this, $name], $params); } public static function __callStatic($name, $params) { return call_user_func_array([new static(), $name], $params); } } class Cache extends Facades { protected function fn($a, $b) { echo "function parameters: ${a} and ${b}<br>"; } protected function static_fn($a, $b) { echo "static function parameters: ${a} and ${b}<br>"; } } } namespace { class Autoload { public $aliases; public function __construct($aliases = []) { $this->aliases = $aliases; } public function register() { spl_autoload_register([$this, 'load'], true, true); return $this; } public function load($alias) { if (isset($this->aliases[$alias])) { return class_alias($this->aliases[$alias], $alias); } } } $aliases = [ 'Cache' => Illuminate\Support\Facades\Cache::class, ]; $autoloader = (new Autoload($aliases))->register(); Cache::fn(3,6); Cache::static_fn(4,7); }

    ======================分割线======================

    其实 Facade 这个名字还有深层次的体现,不但可以理解为“门面”,也可以解释为“伪装”。

    Cache 的背后不一定真的是 Cache,打开门面,撕开伪装才能看到真相。

    在我对 laravel 5.1.31 LTS 源码注释中可以一窥究竟:

    github: https://github.com/yufangcheng/laravel-5.1.31-LTS-with-comment/blob/master/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php

    转载请注明原文地址: https://ju.6miu.com/read-676981.html

    最新回复(0)