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,比如通常用的Route
,Cache
,Request
,Session
等等,很多小伙伴在使用的时候都会写完整的命名空间,包括 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\Application
的 bootstrapWith
方法,这个方法会做一些容器的引导工作,比如加载环境变量、加载配置、配置异常 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.php
中 aliases
数组,类似于:
'App' => Illuminate\Support\Facades\App::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
...
AliasLoader
的 register
方法注册了 AliasLoader
的 load
方法到 SPL __autoload 函数队列中。
spl_autoload_register([$this, 'load'], true, true);
AliasLoader
的 load
方法通过 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 别名是一样的。
AliasLoader
的 load
方法注册到 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 协议》,转载必须注明作者和本文链接
为什么没人评论?