laravel5.5框架解析[3]——响应Request的流程

laravel5.5框架解析系列文章属于对laravel5.5框架源码分析,如有需要,建议按顺序阅读该系列文章, 不定期更新,欢迎关注

掌握laravel应用的代码执行流程, 对解决项目构建过程中遇到的一些疑难杂症大有裨益.

index.php

作为一个单入口的应用, 想要了解执行流程当然是去看index.php咯

// 记录一下框架启动时间, 可以看一次请求花了多长时间来响应
define('LARAVEL_START', microtime(true));

// composer自动加载
require __DIR__.'/../vendor/autoload.php';

// 这个bootstrap文件里创建了一个Application实例
$app = require_once __DIR__.'/../bootstrap/app.php';

// 通过容器创建了一个http kernel
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

// Request类通过全局变量创建了一个Request实例,
// 通过调用kernel的handle方法, 就得到了一个response
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
// 把response内容发送到浏览器
$response->send();

// 执行一些耗时的后续工作
$kernel->terminate($request, $response);

好简单有木有:)

如何启动Application

创建应用实例

创建Application实例的bootstrap.php代码如下


// 传入项目目录,实例化Application, 即容器. Application继承自Container
$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);


// 绑定http kernel实现类, 单例模式
$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

// 绑定 console kernel 实现类
$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

// 绑定异常处理实现类
$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;

好像没啥步骤啊, 为啥还把创建Application单独写一个文件, 不放在index.php里? 因为index.php是由cgi进程执行的,但是你可能需要在cli环境下运行Application哟, 比如测试, 和artisan. 所以就单独放在文件, 需要的地方再require.

你可能奇怪了怎么没有注册绑定那些service啊? 请继续往下看

Application 初始化

Application代码

public function __construct($basePath = null)
{
    // 项目目录, 很多地方要用
    if ($basePath) {
        $this->setBasePath($basePath);
    }
   
    // 基本绑定
    $this->registerBaseBindings();
    
    // 基本service绑定
    $this->registerBaseServiceProviders();

    // 别名
    $this->registerCoreContainerAliases();
}

protected function registerBaseBindings()
{
    // 把实例存在类里边
    static::setInstance($this);
    
    // 把自己放到容器
    $this->instance('app', $this);
    
    // 把自己放到容器again, 并绑到Container的实现
    $this->instance(Container::class, $this);
    
    // PackageManifest,这个东西是laravel5.5新增的,
    // 5.5 安装拓展包, 不需要手动配provider了(如果拓展包支持的话),
    // 他会从拓展包的composer.json extra配置中读取.
    // 个人认为其实可以通过composer插件的方式安装拓展包,
    // 这样可以在安装阶段配置service provider, 而不是运行阶段
    $this->instance(PackageManifest::class, new PackageManifest(
        new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
    ));
}

// 加载基本的service provider, 启动模块
protected function registerBaseServiceProviders()
{
    // 事件模块
    $this->register(new EventServiceProvider($this));
    
    // log模块
    $this->register(new LogServiceProvider($this));

    // 路由模块
    $this->register(new RoutingServiceProvider($this));
}

// 这就把web组件都给绑定到实现类并注册了一个别名, 然后你就可以各种app('config'), $app['config'], $app->make('config')
public function registerCoreContainerAliases()
{
    foreach ([
        'app'                  => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class,  \Psr\Container\ContainerInterface::class],
        'auth'                 => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
        'auth.driver'          => [\Illuminate\Contracts\Auth\Guard::class],
        'blade.compiler'       => [\Illuminate\View\Compilers\BladeCompiler::class],
        'cache'                => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
        'cache.store'          => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
        'config'               => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
        'cookie'               => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
        'encrypter'            => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
        'db'                   => [\Illuminate\Database\DatabaseManager::class],
        'db.connection'        => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
        'events'               => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
        'files'                => [\Illuminate\Filesystem\Filesystem::class],
        'filesystem'           => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
        'filesystem.disk'      => [\Illuminate\Contracts\Filesystem\Filesystem::class],
        'filesystem.cloud'     => [\Illuminate\Contracts\Filesystem\Cloud::class],
        'hash'                 => [\Illuminate\Contracts\Hashing\Hasher::class],
        'translator'           => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
        'log'                  => [\Illuminate\Log\Writer::class, \Illuminate\Contracts\Logging\Log::class, \Psr\Log\LoggerInterface::class],
        'mailer'               => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
        'auth.password'        => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
        'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
        'queue'                => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
        'queue.connection'     => [\Illuminate\Contracts\Queue\Queue::class],
        'queue.failer'         => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
        'redirect'             => [\Illuminate\Routing\Redirector::class],
        'redis'                => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
        'request'              => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
        'router'               => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
        'session'              => [\Illuminate\Session\SessionManager::class],
        'session.store'        => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
        'url'                  => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
        'validator'            => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
        'view'                 => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
    ] as $key => $aliases) {
        foreach ($aliases as $alias) {
            $this->alias($key, $alias);
        }
    }
}

Application 貌似启动完成, 看一下这个时候容器里有啥
在index.php里 执行

$app = require_once __DIR__.'/../bootstrap/app.php';
// ------------- 加入下面这些 -----------
$rf = new ReflectionClass(\Illuminate\Container\Container::class);
$p = $rf->getProperty('resolved');
$p->setAccessible(true);
dd($p->getValue($app));

浏览器输出结果

[]

哈哈, 啥都没有. 虽然只是注册了服务,并未resolve, 可是之前不是有注册service provider吗, 难道这个也不需要resolve?
实际上此时service provider都还没有被resolve并执行. 那么真正执行在哪儿呢, 实际上在http kernel的handle 流程里, 请看下节

handle($request)

一个request到底经历了怎样的千难万险,才得以历练出正确的response呢? 来一探究竟
Illuminate\Foundation\Http\Kernel :

public function handle($request)
{
    try {
        // 开启请求method覆盖, 就是文档里提到的如何在不支持的浏览器里发送delete等请求
        $request->enableHttpMethodParameterOverride();
        // 把请求发送给路由, 得到response, 详情在下边
        $response = $this->sendRequestThroughRouter($request);
    } catch (Exception $e) { // Request->Response途中遇到异常, 在这进行处理
        // 记录日志
        $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)
{
    // request 保存到容器
    $this->app->instance('request', $request);
    
    // 门面缓存清除, 因为门面会从容器中取实例然后缓存,
    // 刚刚刷新了容器中的Request, 为了让facade能更新实例, 就清楚缓存
    Facade::clearResolvedInstance('request');

    // 这个就是前面提到的会在handle流程里初始化Application的service provider
    // 为什么Application 会放在这启动呢? 因为不同情况下Application需要的加载不同的基础service provider,
    // 所以就没有放在Application中启动, 而是提供了bootstrapWith的公开方法,
    // 供外部按需传入service provider 进行启动, 这里传入了下面这些provider
    //  用于加载 .env 配置文件
    // \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
    // 加载config文件夹下配置
    // \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
    // 注册异常处理, laravel5.5 用whoops来渲染异常
    // \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
    // 注册门面服务
    // \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
    // laravel5.5 新功能, 注册通过composer.json来提供provider类的 provider
    // \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
    // register 完毕之后, 最后boot注册过的service provider
    // \Illuminate\Foundation\Bootstrap\BootProviders::class,
    $this->bootstrap();

    // 最后通过pipeline, 把Request经过全局中间件, 发送到路由分发过程
    // 后面的文章会有pipeline实现原理分析
    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}

下面是如何将Request发送到匹配路由 Illuminate\Routing\Router

public function dispatchToRoute(Request $request)
{
    // 通过Request匹配到路由, 匹配不到直接抛异常, 而不是返回null.
    // 是不是返回null ,在这里在抛 404 更合理呢?
    $route = $this->findRoute($request);
    
    // 把route绑到request上, 这样在其他地方, 你可以通过request 获取到匹配的路由
    $request->setRouteResolver(function () use ($route) {
        return $route;
    });
    
    // 触发路由匹配事件
    $this->events->dispatch(new Events\RouteMatched($route, $request));
    // 执行路由, 得到相应
    $response = $this->runRouteWithinStack($route, $request);
    // 把控制器或者路由级中间件返回的结果(可能是array,string, int 或其他类型), 转换成Response实例
    return $this->prepareResponse($request, $response);
}
// router中执行路由的过程
protected function runRouteWithinStack(Route $route, Request $request)
{
    // 是否跳过中间件, 特殊情况下, 或者测试时有可能需要跳过
    $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                            $this->container->make('middleware.disable') === true;
    //取出路由中间件
    $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
    
    // 把Request过一遍路由中间件, 然后执行路由,
    // 这里也调用了prepareResponse, 上面那也调用了, 为什么会调用2次? 有趣吧,哈哈.
    // 这里的response其实是返回给全局中间件那里去了, 而全局中间件可能会根据response的内容,
    // 决定不予发送给浏览器, 而是自己发送了一个其他响应, 比如返回一个数组[code=>500, msg=>'sth. went wrong'],
    // 所以, 经过全局回来的response还要再prepare一遍. 且保证中间件的handle流程里$next()返回的是一个Response实例
    return (new Pipeline($this->container))
                    ->send($request)
                    ->through($middleware)
                    ->then(function ($request) use ($route) {
                        return $this->prepareResponse(
                            $request, $route->run()
                        );
                    });
}

terminate()

pipeline管道是有始有终的, 从哪里进去, 就得从哪里出来, 不过大变活人, Request进去, Response出来了.所以相应最后到了http kernel 的handle方法里,在index.php中,相应被发送到浏览器
最后执行terminal方法 Http\Kernel

public function terminate($request, $response)
{
    // 注册的中间件如果有terminate调用terminate方法
    // session 存盘就是在中间件terminate中完成的, 所以很多人在controller
    // 中使用了dd()函数, 就发现session出问题了. 因为dd()会使程序直接退出,
    // 这时候请使用dump()来输出变量
    $this->terminateMiddleware($request, $response);
    // Application的terminate, 他会调用通过terminating方法注册的回调
    $this->app->terminate();
}

你可能感兴趣的:(laravel5.5框架解析[3]——响应Request的流程)