照猫画虎 实现 min-laravel 框架系列之 Facades
- laravel
- 2020-06-15
- 250
- 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 类,
<?phpnamespace 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();