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();
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 先说几句废话,调和气氛。事情的起由来自客户需求频繁变更,伟大的师傅决定横刀立马的改革使用新的框架(created ...
    wsdadan阅读 3,036评论 0 12
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • 前阵子看了点Laravel源码,越看越乱,网上大部分中文文档都是直译,比较生涩难懂,还是决定看英文文档顺便就我的理...
    Bill_Wang阅读 334评论 0 2
  • 最近在和同学参与一个创业项目,用到了laravel,仔细研究了一下,发现laravel封装了很多开箱即用的方法,通...
    MakingChoice阅读 3,298评论 0 0
  • 反思日志0617 周六 晴 画图 第一次独立完成画图,文老师点评橙和橙红,群青和青紫需要再调一点红。文老师果然厉害...
    娟妹纸李娟阅读 158评论 5 0