前言 在使用 Laravel 框架进行开发的时候,Facades
是一个经常能用到的模块,比如在使用缓存(Cache)、日志(Log) 等组件的时候。
1 2 3 4 5 6 7 use Illuminate \Support \Facades \Cache ;$name = Cache::get('name' ); use use Illuminate \Support \Facades \Log ;Log::info('this is log content' );
Facades
的主要优点就是不需要记住各个组件所在目录对应的的命名空间,因为 Illuminate\Support\Facades\
这一段都是固定的,变化的只是后面的组件名称。
早在刚接触 Laravel 的时候,就对 Facades
充满了疑惑,为什么要这样用,直接用组件真正的命名空间不行吗,代码追踪过去又没有组件实现代码,只有一个静态方法 getFacadeAccessor
返回了组件的名称(cache、log),但是在使用的时候又能调用到组件真正的方法。
1 2 3 4 5 6 7 8 9 10 11 12 class Cache extends Facade { protected static function getFacadeAccessor () { return 'cache' ; } }
那么真相只能在父类 Facade
里面了。
但是鉴于平时工作都在搬砖,做一名快乐的 CURD Boy
,秉承着实用主义
及又不是不能用
的理念,所以当时并没有去深究代码(实际上是因为太菜了看不懂)。
那为什么现在又开始研究怎么它实现的呢?是我变强了吗?不!是因为需要用上它了…(实用主义万岁)
当时在写一个基于 overtrue/socialite 实现的 her-cat/colourlife-oauth2 扩展包,为了兼容 Laravel 的 Facades
用法,不得不了解一下 Facades
的实现原理(其实仔细看两遍文档就能知道个大概了)。
实现原理 言归正传,就拿 Cache
来举例子,Facades\Cache
的文件内容就在上面,Cache 继承了抽象类 Facade
并实现了 getFacadeAccessor
静态方法,该方法返回了 Cache 组件
(就是真正的缓存类)在 Laravel 容器中注册的名称,也就是 cache
,这样我们就能从容器中取出 Cache 组件
的实例对象了,整个 Facades
实现也就是从容器中取出实例对象,让实例对象执行被调用的方法。
vendor/laravel/framework/src/Illuminate/Foundation/Application.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public function registerCoreContainerAliases () { foreach ([ 'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class], ] as $key => $aliases) { foreach ($aliases as $alias) { $this ->alias($key, $alias); } } }
当我们运行 Cache::get('name')
的时候,会先触发父类 Facade
的魔术方法 __callStatic
,因为 Facade
并没有 get
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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); }
$method
是被调用的方法的名称(这个时候的值是 get
),$args
就是参数数组(array(1) { [0]=> string(4) "name" }
),通过 getFacadeRoot
方法获取到 Cache 组件
的实例对象,然后以 $args
作为参数,调用实例的 get
方法,最后返回方法执行结果。
以上就是方法就是 Facade
的实现原理了,接下来再看看 getFacadeRoot
方法。
1 2 3 4 5 6 7 8 9 public static function getFacadeRoot () { return static ::resolveFacadeInstance(static ::getFacadeAccessor()); }
这个方法本身没有什么代码,就是调用了 resolveFacadeInstance
方法,通过组件名称从 Laravel 容器中取出对应的实例对象,这个组件名称就是通过 Facades\Cache
类的 getFacadeAccessor
方法获取的,这里的返回值就是 cache
。
接下来就到了最后一个方法:resolveFacadeInstance
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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]; } }
此时 $name
的值是 cache
。如果 $name
是一个对象的话,就直接返回 $name
,$resolvedInstance
是一个用来保存已解析过的实例对象的数组,判断 $name
是否已经从 Laravel 容器中解析过,如果已经解析过就直接返回。
注意:这里是的判断是在这一次请求的生命周期内是否解析过,下次请求进来的时候还是会从 Laravel 容器中取出来。
如果 Laravel 容器如果不是空的话,就通过 $name
从 Laravel 容器中取出 Cache 组件
的实例对象,将 $name
作为 key 存入解析过的实例对象的数组中,并返回。
精简版实现及测试代码 好了,Facades
的实现代码到这里就完了,最后再附上精简版的 Facades
及测试代码。
Facade
抽象类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 namespace App \Core ;abstract class Facade { protected static $app = []; protected static $resolvedInstance = []; public static function getFacadeRoot () { return static ::resolveFacadeInstance(static ::getFacadeAccessor()); } protected static function getFacadeAccessor () { throw new \Exception ('Facade does not implement getFacadeAccessor method.' ); } 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]; } } public static function __callStatic ($method, $args) { $instance = static ::getFacadeRoot(); if (! $instance) { throw new \Exception ('A facade root has not been set.' ); } return $instance->$method(...$args); } public static function setComponentInstance ($name, $obj) { self ::$app[$name] = $obj; } }
继承了 Facade
的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 namespace App \Facades ;use App \Core \Facade ;class Cache extends Facade { public static function getFacadeAccessor () { return 'cache' ; } } class Log extends Facade { public static function getFacadeAccessor () { return new \App\Components\Log(); } }
组件的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 namespace App \Components ;class Cache { public function get ($key) { return "key:{$key}, value: her-cat" ; } } class Log { public function info ($content) { return "记录的内容:{$content}" ; } }
测试代码:
1 2 3 4 5 6 7 8 9 namespace App \Test ;\App\Core\Facade::setComponentInstance('cache' , new \App\Components\Cache()); echo \App\Facades\Cache::get('name' ).PHP_EOL;echo \App\Facades\Log::info('哈哈哈哈' ).PHP_EOL;