照猫画虎 实现 min-laravel 框架系列之 Facades

php   laravel  

Facades

Facades 为应用的服务容器提供了一个「静态」 接口。Laravel 自带了很多 Facades,可以访问绝大部分功能。Laravel Facades 实际是服务容器中底层类的 「静态代理」 ,相对于传统静态方法,在使用时能够提供更加灵活、更加易于测试、更加优雅的语法。

通过 facades 和 alias ,通过向系统注册自动加载函数,实现类的简单调用模式和一套类文件寻找机制

Illuminate\Foundation\AliasLoader 类

该类是一个单列模式的类,主要功能是加载 config/app.php 中定义的别名数组,并且向系统注册自动加载函数

单例

  1. // 构造函数私有化
  2. private function __construct($aliases)
  3. {
  4. $this->aliases = $aliases;
  5. }
  6. // 对外方法,获取实例
  7. public static function getInstance(array $aliases = [])
  8. {
  9. if (is_null(static::$instance)) {
  10. return static::$instance = new static($aliases);
  11. }
  12. $aliases = array_merge(static::$instance->getAliases(), $aliases);
  13. static::$instance->setAliases($aliases);
  14. return static::$instance;
  15. }

自动加载函数

  1. // 控制只注册一次
  2. public function register()
  3. {
  4. if (! $this->registered) {
  5. $this->prependToLoaderStack();
  6. $this->registered = true;
  7. }
  8. }
  9. // 注册自动加载函数
  10. protected function prependToLoaderStack()
  11. {
  12. spl_autoload_register([$this, 'load'], true, true);
  13. }
  14. // 实际自动加载的函数
  15. public function load($alias)
  16. {
  17. // 实时 Facades 的实现
  18. if (static::$facadeNamespace && strpos($alias, static::$facadeNamespace) === 0) {
  19. $this->loadFacade($alias);
  20. return true;
  21. }
  22. // config/app.php 中 aliases 定义的 facade。
  23. if (isset($this->aliases[$alias])) {
  24. // 基于用户定义的类 original 创建别名 alias。 这个别名类和原有的类完全相同。但三个参数默认为 true,表示 如果原始类没有加载,会使用自动加载
  25. return class_alias($this->aliases[$alias], $alias);
  26. }
  27. }

实时 facade 实现

  1. protected function loadFacade($alias)
  2. {
  3. require $this->ensureFacadeExists($alias);
  4. }
  5. protected function ensureFacadeExists($alias)
  6. {
  7. // 会判断是否有生成过
  8. if (file_exists($path = storage_path('framework/cache/facade-'.sha1($alias).'.php'))) {
  9. return $path;
  10. }
  11. // 依据模板,生成 facade 类
  12. file_put_contents($path, $this->formatFacadeStub(
  13. $alias, file_get_contents(__DIR__.'/stubs/facade.stub')
  14. ));
  15. return $path;
  16. }
  17. protected function formatFacadeStub($alias, $stub)
  18. {
  19. $replacements = [
  20. str_replace('/', '\\', dirname(str_replace('\\', '/', $alias))),
  21. class_basename($alias),
  22. substr($alias, strlen(static::$facadeNamespace)),
  23. ];
  24. // 对模板类中的几个关键词进行替换
  25. return str_replace(
  26. ['DummyNamespace', 'DummyClass', 'DummyTarget'], $replacements, $stub
  27. );
  28. }

抽象类 Illuminate\Support\Facades\Facade

所有系统定义和用户自定义的 facade,都需要继承抽象类 Facade,并且实现自己的 getFacadeAccessor 方法,该方法返回一个字符串,并且 laravel 的服务容器可以依据此字符串解析出所需要的类

魔术方法

当调用不存在的静态类方法时,php 会自动调用此函数

  1. public static function __callStatic($method, $args)
  2. {
  3. // 利用服务容器解析出类
  4. $instance = static::getFacadeRoot();
  5. if (! $instance) {
  6. throw new RuntimeException('A facade root has not been set.');
  7. }
  8. // 调用对应的方法
  9. return $instance->$method(...$args);
  10. }

解析类

  1. // 如果子类没有重写此方法,就会抛出异常
  2. protected static function getFacadeAccessor()
  3. {
  4. throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
  5. }
  6. public static function getFacadeRoot()
  7. {
  8. return static::resolveFacadeInstance(static::getFacadeAccessor());
  9. }
  10. // 通过 name ,尝试去解析类
  11. protected static function resolveFacadeInstance($name)
  12. {
  13. // 本身就是一个对象,则直接返回
  14. if (is_object($name)) {
  15. return $name;
  16. }
  17. // 找缓存,
  18. if (isset(static::$resolvedInstance[$name])) {
  19. return static::$resolvedInstance[$name];
  20. }
  21. // 通过服务容器去解析类
  22. if (static::$app) {
  23. return static::$resolvedInstance[$name] = static::$app[$name];
  24. }
  25. }

服务的启动和加载过程

1. Illuminate\Foundation\Bootstrap\RegisterFacades 类的 bootstrap

作为 http 引导启动的核心服务,在处理请求时进行引导启动

  1. public function bootstrap( Application $app )
  2. {
  3. // 清除缓存
  4. Facade::clearResolvedInstances();
  5. // 设置参数
  6. Facade::setFacadeApplication( $app );
  7. // 获取自动加载类,进行系统定义的 facade 的加载
  8. AliasLoader::getInstance(array_merge(
  9. $app->make('config')->get('app.aliases', []),
  10. // $app->make(PackageManifest::class)->aliases() 扩展包自动发现机制
  11. []
  12. ))->register();
  13. }

使用系统定义的 facade

  1. 在 index.php 文件中添加 facade 使用

    1. echo App::make("config")->get('app.timezone');
  2. 系统在解析 App 类时,发现该类没有被 require ,就会依次调用之前设置的所有的自动加载函数,即 AliasLoader::load 方法,说明:在这里会被调用两次

    • 在找 App 类时,因为在 config/app.php 中定义了别名,所以在 load 的函数会调用 php 的 class_alias 别名设置函数,由于 App 类本身也没有被加载,所以会再次触发自动加载函数的调用
    • 接着会使用 Illuminate\Support\Facades\App ,再一次依次调用所有的自动加载函数
  3. 找到类 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 类,

  1. <?php
  2. namespace DummyNamespace;
  3. use Illuminate\Support\Facades\Facade;
  4. /**
  5. * @see \DummyTarget
  6. */
  7. class DummyClass extends Facade
  8. {
  9. /**
  10. * Get the registered name of the component.
  11. *
  12. * @return string
  13. */
  14. protected static function getFacadeAccessor()
  15. {
  16. return 'DummyTarget';
  17. }
  18. }

生成 facade 类

AliasLoader::loadFacade 方法,生成对应的文件

使用

在 index.php 中

  • 引入命名空间

    1. use Facades\App\Events\OrderEvent;
  • 直接使用类名的静态调用

    1. echo OrderEvent::getId();


评论 0

发表评论

Top