照猫画虎 实现 min-laravel 框架系列之 Facades
- laravel
- 2020-06-15
- 3395
- 0
Facades
Facades 为应用的服务容器提供了一个「静态」 接口。Laravel 自带了很多 Facades,可以访问绝大部分功能。Laravel Facades 实际是服务容器中底层类的 「静态代理」 ,相对于传统静态方法,在使用时能够提供更加灵活、更加易于测试、更加优雅的语法。
通过 facades 和 alias ,通过向系统注册自动加载函数,实现类的简单调用模式和一套类文件寻找机制
Illuminate\Foundation\AliasLoader 类
该类是一个单列模式的类,主要功能是加载 config/app.php 中定义的别名数组,并且向系统注册自动加载函数
单例
// 构造函数私有化
private function __construct($aliases)
{
$this->aliases = $aliases;
}
// 对外方法,获取实例
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()
{
spl_autoload_register([$this, 'load'], true, true);
}
// 实际自动加载的函数
public function load($alias)
{
// 实时 Facades 的实现
if (static::$facadeNamespace && strpos($alias, static::$facadeNamespace) === 0) {
$this->loadFacade($alias);
return true;
}
// config/app.php 中 aliases 定义的 facade。
if (isset($this->aliases[$alias])) {
// 基于用户定义的类 original 创建别名 alias。 这个别名类和原有的类完全相同。但三个参数默认为 true,表示 如果原始类没有加载,会使用自动加载
return class_alias($this->aliases[$alias], $alias);
}
}
实时 facade 实现
protected function loadFacade($alias)
{
require $this->ensureFacadeExists($alias);
}
protected function ensureFacadeExists($alias)
{
// 会判断是否有生成过
if (file_exists($path = storage_path('framework/cache/facade-'.sha1($alias).'.php'))) {
return $path;
}
// 依据模板,生成 facade 类
file_put_contents($path, $this->formatFacadeStub(
$alias, file_get_contents(__DIR__.'/stubs/facade.stub')
));
return $path;
}
protected function formatFacadeStub($alias, $stub)
{
$replacements = [
str_replace('/', '\\', dirname(str_replace('\\', '/', $alias))),
class_basename($alias),
substr($alias, strlen(static::$facadeNamespace)),
];
// 对模板类中的几个关键词进行替换
return str_replace(
['DummyNamespace', 'DummyClass', 'DummyTarget'], $replacements, $stub
);
}
抽象类 Illuminate\Support\Facades\Facade
所有系统定义和用户自定义的 facade,都需要继承抽象类 Facade,并且实现自己的 getFacadeAccessor 方法,该方法返回一个字符串,并且 laravel 的服务容器可以依据此字符串解析出所需要的类
魔术方法
当调用不存在的静态类方法时,php 会自动调用此函数
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);
}
解析类
// 如果子类没有重写此方法,就会抛出异常
protected static function getFacadeAccessor()
{
throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
}
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
// 通过 name ,尝试去解析类
protected static function resolveFacadeInstance($name)
{
// 本身就是一个对象,则直接返回
if (is_object($name)) {
return $name;
}
// 找缓存,
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
// 通过服务容器去解析类
if (static::$app) {
return static::$resolvedInstance[$name] = static::$app[$name];
}
}
服务的启动和加载过程
1. Illuminate\Foundation\Bootstrap\RegisterFacades 类的 bootstrap
作为 http 引导启动的核心服务,在处理请求时进行引导启动
public function bootstrap( Application $app )
{
// 清除缓存
Facade::clearResolvedInstances();
// 设置参数
Facade::setFacadeApplication( $app );
// 获取自动加载类,进行系统定义的 facade 的加载
AliasLoader::getInstance(array_merge(
$app->make('config')->get('app.aliases', []),
// $app->make(PackageManifest::class)->aliases() 扩展包自动发现机制
[]
))->register();
}
使用系统定义的 facade
在 index.php 文件中添加 facade 使用
echo App::make("config")->get('app.timezone');
系统在解析 App 类时,发现该类没有被 require ,就会依次调用之前设置的所有的自动加载函数,即 AliasLoader::load 方法,说明:在这里会被调用两次
- 在找 App 类时,因为在 config/app.php 中定义了别名,所以在 load 的函数会调用 php 的 class_alias 别名设置函数,由于 App 类本身也没有被加载,所以会再次触发自动加载函数的调用
- 接着会使用 Illuminate\Support\Facades\App ,再一次依次调用所有的自动加载函数
找到类 Illuminate\Support\Facades\App 之后,就会调用其父类( Illuminate\Support\Facades\Facade ) 的静态魔术方法,通过 laravel 的服务容器对 getFacadeAccessor 方法的返回进行解析,最后得到绑定到服务容器的 app 的实现类 application 类。得到类之后再去调用后续的方法,
实时 facades
在 laravel 中,任何以 facade 开头的命名空间,AliasLoader::load 函数都会自动创建对应的 facade 实现类,并将文件保存在 storage/framework/cache/ 文件目录下,在实现上,需要手动创建改目录文件
模板类文件
facade.stub 是一个标准 facade 类的模板,当某个未加载的类的命名空间以 facade 开头,laravel 框架会使用该模板类,通过简单的关键词替换,生成对应的 facade 类,
<?php
namespace DummyNamespace;
use Illuminate\Support\Facades\Facade;
/**
* @see \DummyTarget
*/
class DummyClass extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'DummyTarget';
}
}
生成 facade 类
AliasLoader::loadFacade 方法,生成对应的文件
使用
在 index.php 中
引入命名空间
use Facades\App\Events\OrderEvent;
直接使用类名的静态调用
echo OrderEvent::getId();