Facade
前言
使用过laravel的同学对于DB::table(‘XXX’)这样的写法一定不陌生吧,但是最开始这样写有没有感觉怪怪的呢,DB类里根本没有我们调用的table方法?更奇怪的是不用use DB,编辑器提示DB不存在,但是程序运行起来依然没有问题,接下来我们就看看这个简单的写法是如何实现的。
概念
我看好多地方都把Facade叫做门面有点不太能懂,个人更喜欢把Facade理解为服务的代理,他其实就是做了简单一个转发。比如访问DB,其实就是Facade/DB将这个请求转发给$app['db']或者DatabaseManager这个类。下边是laravel文档中的解释
Facades 为应用程序的 服务容器 中可用的类提供了一个「静态」接口。Laravel 本身附带许多的 facades,甚至你可能在不知情的状况下已经在使用他们!Laravel 「facades」作为在服务容器内基类的「静态代理」,拥有简洁、易表达的语法优点,同时维持着比传统静态方法更高的可测试性和灵活性。
实现
db的注册
数据库服务提供者DatabaseServiceProvider的register中注册了db,供Facade/DB引导时使用。这里的注册就不详细说了,可以参考Laravel核心代码学习 -- 服务提供器
class DatabaseServiceProvider extends ServiceProvider
{
public function register()
{
Model::clearBootedModels();
$this->registerConnectionServices();
}
protected function registerConnectionServices()
{
$this->app->singleton('db.factory', function ($app) {
return new ConnectionFactory($app);
});
$this->app->singleton('db', function ($app) {
return new DatabaseManager($app, $app['db.factory']); //这里注册了db
});
$this->app->bind('db.connection', function ($app) {
return $app['db']->connection();
});
}
}
Facade的别名
先来看看在不使用use DB的情况下,程序是如何找到Facade/DB这个类的。这里的Facade的别名处理就是发生引导时触发的。注意这里如果是在一个类中使用DB在不使用use时一定要用\DB::table('XXX'),因为如果不添加\默认是加载当前类的命名空间下的DB。
// index.php
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle( //处理请求
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);
在kernel的handle中进行了引导
public function handle($request)
{
$this->sendRequestThroughRouter($request); //通过路由进行相应的处理
$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);
return $response;
}
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap(); //在处理路由逻辑时先完成程序的引导
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$bootstrappers = [
\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,
];
$this->app->bootstrapWith($bootstrappers());
}
}
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
foreach ($bootstrappers as $bootstrapper) {
$this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
$this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
}
}
// \Illuminate\Foundation\Bootstrap\RegisterFacades::class
public function bootstrap(Application $app)
{
Facade::clearResolvedInstances();
Facade::setFacadeApplication($app);
AliasLoader::getInstance(array_merge(
$app->make('config')->get('app.aliases', []), //获取config/app中aliases数组
$app->make(PackageManifest::class)->aliases()
))->register();
}
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)
{
if (isset($this->aliases[$alias])) { //这里就解决了即使不use DB,依然可以访问到Facade/DB
return class_alias($this->aliases[$alias], $alias);
}
}
方法的调用
我们在看看这个不存在的table是如何执行的,所有的Facade都必须继承abstract class Facade这个抽象类并且重写getFacadeAccessor这个方法,抽象类中有__callStatic这个抽象方法。当调用一个不存在的静态方法时,则会触发该方法。
class DB extends Facade
{
protected static function getFacadeAccessor() //重写父类的方法,返回字符串,这个字符串其实就是这个Facade代理的服务在容器中注册时用的abstruct
{
return 'db';
}
}
abstract class Facade
{
public static function __callStatic($method, $args) //当调用一个不存在的静态方法时,则会触发该方法
{
$instance = static::getFacadeRoot(); //一个实例,DatabaseManager
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
return $instance->$method(...$args); //调用DatabaseManager这个类中的table方法。
}
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
protected static function resolveFacadeInstance($name)
{
return staic::$app[$name]; //返回服务容器的$app['db']
}
}
最后
说了这么多其实DB::table('XXX'),就等于$app->make('db')->table('XXX')。Facade和服务提供者之前关系紧密,学习时两者要结合看,本来打算写一篇关于服务提供者的文章,结果发现自己对他理解有限,就暂时放弃了以后有机会再写。