php request render,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();

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值