Laravel 5.4 real-time facade 探究


在 laravel 5.4 添加了一个 realtime facade 的功能,相比 5.4 之前的 facade,我们使用时不再需要显式的添加一个 Facade 类,而是通过在使用的时候加上 Facades 命名空间就行,比如有一个 App\Services\RealTimeService,我们可以像下面这样使用 realtime facade:

use Facades\App\Services\RealTimeService;

...

RealTimeService::someAction();

...

Laravel 5.4 是如何实现的呢?一起来看看。

Facade 的工作原理

Facade 的工作原理很简单,通过 __callStatic 魔术方法,在调用 Facade 的静态方法时动态的通过依赖注入容器获取访问器的实例,然后调用实例的相应方法,实现 “静态代理” 的效果。

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

    return $instance->$method(...$args);
}
  • Facade 的 getFacadeAccessor 的方法返回的是访问器在容器中的注册名,容器通过这个注册名获取访问器
  • 通过 Facade 调用的类是同一个实例,类似于单例

使用 Facade 的“正确”姿势

Laravel 中内置了很多 Facade,比如通常用的RouteCacheRequestSession 等等,很多小伙伴在使用的时候都会写完整的命名空间,包括 Laravel generator 生成的文件有些也使用了完整的类名。

use Illuminate\Support\Facades\Route;

Route::get(...);

实际上这些内置的 facade 是可以直接通过别名来使用的,比如上面的例子就可以这样使用:

use Route;

Route::get(...);

文档中对 Facade 的介绍也是这样使用,而不会写完整的命名空间。我在刚学习 laravel 的时候用的是 sublime,因为没有自动导入功能,为了少写些代码,facade 都是直接用别名,所以对这个印象很深(现在很多新手都是用 phpstorm,有自动导入功能,这种用法反而一直不知道)。那个时候一直很奇怪,明明没有这个类,到底是如何实现的?

Facade 别名如何实现的

先看看为什么可以直接使用根命名空间的 Facade 别名.

config\app.php 中有个 aliases 数组,这个数组定义了别名以及实际对应的 Facade。

...
'Route' => Illuminate\Support\Facades\Route::class,
...

Http\Kernel 处理请求之前,会调用 Illuminate\Foundation\ApplicationbootstrapWith 方法,这个方法会做一些容器的引导工作,比如加载环境变量、加载配置、配置异常 handle、注册 Facades、注册 ServiceProvider、启动 ServiceProvider 等等,具体的类如下:

\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,

很显然,Facade 相关注册工作在 \Illuminate\Foundation\Bootstrap\RegisterFacades::class 中定义,我们具体看看这个类:

public function bootstrap(Application $app)
{
    Facade::clearResolvedInstances();

    Facade::setFacadeApplication($app);

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

获取 Illuminate\Foundation\AliasLoader 的实例并调用 register 方法注册。

AliasLoader 维护了一个 Facade 别名以及对应的 Facade 的数组,也就是 config\app.phpaliases 数组,类似于:

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

AliasLoaderregister 方法注册了 AliasLoaderload 方法到 SPL __autoload 函数队列中。

spl_autoload_register([$this, 'load'], true, true);

AliasLoaderload 方法通过 class_alias 函数,为实际的 Facade 创建一个 Facade 别名。

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

所以,看到这里就很明白了: Facade 别名是通过 class_alias 创建,使用 spl_autoload_register 函数让别名实现自动加载

realtime Facade 如何实现的

5.4 版本中的 realtime Facade 是如何实现的?与上面的 Facade 别名是一样的。

AliasLoaderload 方法注册到 SPL __autoload函数队列后,所有的类在查找时都会通过这个方法。当查找的类名是以 Facades\\ 开头的时候,这个时候就会在 storage\framework\cache 中自动创建一个 facade-hash(类名).php 的文件,并 require 这个文件。

比如开头的例子,use Facades\App\Services\RealTimeService 时会在 storage\framework\cache 中创建一个 facade-ababc11941d3eff6bccd4a8e2495a335336eec0b.php 的文件,我们看看这个文件的内容:

<?php

namespace Facades\App\Services;

use Illuminate\Support\Facades\Facade;

/**
 * @see \App\Services\RunTimeFacade
 */
class RunTimeFacade extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'App\Services\RunTimeFacade';
    }
}

靠,这不就是相当于创建了一个 App\Services\RunTimeFacade 的 Facade 吗?

总结

  • 5.4 以前的根命名空间 Facade 和 5.4 中的 Realtime Facade 都是通过把 Illuminate\Foundation\AliasLoader@load 加入到 SPL __autoload 函数队列中来实现的

  • Realtime Facade 其实就是自动创建了一个 Facade

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由 Summer 于 7年前 加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 1
Bin

为什么没人评论?

6年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!