美文网首页laravel5.5框架解析
laravel5.5框架解析[3]——响应Request的流程

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

作者: PeterQ1998 | 来源:发表于2017-09-02 22:35 被阅读0次

    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的流程

        本文链接:https://www.haomeiwen.com/subject/nsxdjxtx.html