阿比安吉

Have a dream and do it

Laravel学习-Facade

2019-09-25 Code liseen

在web服务框架学习首要面对的一个问题是路由,Laravel的路由是怎么实现的呢?先总结一下,Laravel暴露给我们的路由其实是一个别名后的Facade,那我们就先看看什么是Facade吧

public/index.php中是Laravel的启动目录所以我们先看一下index.php文件内容

<?php

// 引入引导
require __DIR__.'/../vendor/autoload.php';

// 应用程序核心
$app = require_once __DIR__.'/../bootstrap/app.php';

我们的web路由在routes/web.php内定义的

<?php
// get请求路由
Route::get(
    '/',
    function () {
        return 'Index';
    }
);

我们如果安装了ide_help后会发现这个Route实际是一个Facade.路径为\Illuminate\Support\Facades\Route然后我们看一下它的源码

class Route extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'router';
    }
}

可以看到这是一个继承自Facade的子类,其中也只有一个getFacadeAccessor方法返回了一个字符串router那么Route::get()是怎么来的呢?我们继续看下Facade发现也没有所谓的get()方法,但是我们却在类的最下面发现了一个php的魔术方法__callStatic


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);
}

查阅资料后发现这个魔术方法是指当调用类的不存在静态方法时会触发这个方法的调用,好那我个人理解就是get()这个方法实际上在这里被调用了.然后我们继续看下getFacadeRoot方法

public static function getFacadeRoot()
{
    return static::resolveFacadeInstance(static::getFacadeAccessor());
}

这个方法又调用了resolveFacadeInstance这个方法内接收了getFacadeAccessor函数的返回.回忆一下getFacadeAccessor是不是在刚才Route子类内覆盖过并返回一个字符串route,好那么继续看resolveFacadeInstance实现

protected static function resolveFacadeInstance($name)
{
    if (is_object($name)) {
        return $name;
    }
    // 这里做了一层缓存方便快速调用
    if (isset(static::$resolvedInstance[$name])) {
        return static::$resolvedInstance[$name];
    }
    // 直接拿字符串去$app上实例化了
    if (static::$app) {
        return static::$resolvedInstance[$name] = static::$app[$name];
    }
}

看到这里可能会有些懵逼.为啥$app[$name]就返回了实例了.$app又是啥.实际上$app来自于Facade类的静态方法setFacadeApplication

public static function setFacadeApplication($app)
{
    static::$app = $app;
}

那么这个方法什么时候被调用的?我们查找引用发现是在Illuminate/Foundation/Bootstrap/RegisterFacades.php内被调用的

class RegisterFacades
{
    public function bootstrap(Application $app)
    {
        Facade::clearResolvedInstances();
        // 看这里...
        Facade::setFacadeApplication($app);
        AliasLoader::getInstance(array_merge(
            $app->make('config')->get('app.aliases', []),
            $app->make(PackageManifest::class)->aliases()
        ))->register();
    }
}

好像越来越复杂了.这个bootstrap又是啥时候被调用的.好吧坚持住.我本人也尝试用查找引用去查发现没找到.然后我又回过头去看初始化的代码发现在bootstrap/app.php代码内除了实例化Application实际还创建了几个注册,其中重要的是App\Http\Kernel

$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

// 注册Kernel到Container
$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

然后我们看下Kernel的实现,发现里面定义了一堆中间件数组,我们先不管这些,继续看它的父类HttpKernel,wow..代码真是多..其实当前有用的就是handle方法,我们先看下实现然后在去看是在哪调用的handle

public function handle($request)
{
    try {
        $request->enableHttpMethodParameterOverride();
        // 主要看这个方法接收了$request参数
        $response = $this->sendRequestThroughRouter($request);
    } catch (Exception $e) {
        $this->reportException($e);

        $response = $this->renderException($request, $e);
    } catch (Throwable $e) {
        $this->reportException($e = new FatalThrowableError($e));

        $response = $this->renderException($request, $e);
    }

    $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()
{
    // 就是这里..先调用了bootstrappers
    if (! $this->app->hasBeenBootstrapped()) {
        $this->app->bootstrapWith($this->bootstrappers());
    }
}
// 这个方法都返回了些啥?
protected function bootstrappers()
{
    return $this->bootstrappers;
}
// 发现什么了么?RegisterFacades对。就是它
protected $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,
];

看了三个方法终于找到了RegisterFacades在哪出现了.然后实际上这个引导数组又作为参数传到了Illuminate/Foundation/Application.php内的bootstrapWith方法内,还是看源码吧.

public function bootstrapWith(array $bootstrappers)
{
    $this->hasBeenBootstrapped = true;

    foreach ($bootstrappers as $bootstrapper) {
        $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);
        // 循环刚才的bootstrap类数组调用了make方法然后调用了bootstrap()
        $this->make($bootstrapper)->bootstrap($this);

        $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
    }
}

实际上make方法内处理也相对复杂,不过针对当前来说就是调用了php的反射ReflectionClass来实例化了类,紧接这调用了RegisterFacades内的bootstrap方法.我们可以回头去看下这个类的实现.好吧,我们终于知道了$app是怎么到Facade内的了。刚才说的handler方法实际是在public/index.php内就被调用了,可以看下代码

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
// 看这里...
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

但是这一切貌似都断层了,说了一大堆Route::get()呢?emmm..看下一篇笔记吧.