建议先看前几篇文章,不然可能会看不懂,这是我的Laravel专栏
【 Laravel_Attitude_do_it的博客-CSDN博客】
感兴趣的可以去瞅一瞅。
上篇我们讲到了路由的分配一直路由绑定方法的执行,现在我们来接着讲Laravel是怎么把路由方法执行的结果返回给客户端的。
我们先回到index.php入口文件:
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
//经过上篇的分析,我们知道这里最终会返回一个\Symfony\Component\HttpFoundation\Response类实例
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
//这里会调用Symfony\Component\HttpFoundation\Response类实例的send方法
$response->send();
$kernel->terminate($request, $response);
来看看send方法都做了什么:
/**
* Sends HTTP headers and content.
* 发送 HTTP headers和内容
* @return $this
*/
public function send()
{
//很明显发送headers,往下看sendHeaders方法
$this->sendHeaders();
//很明显发送内容,往下看sendContent方法
$this->sendContent();
if (\function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
static::closeOutputBuffers(0, true);
}
return $this;
}
/**
* Sends HTTP headers.
* 发送HTTP headers
* @return $this
*/
public function sendHeaders()
{
// headers have already been sent by the developer
//判断headers是否已经发送
if (headers_sent()) {
return $this;
}
// headers
//设置头部基础信息
foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
$replace = 0 === strcasecmp($name, 'Content-Type');
foreach ($values as $value) {
header($name.': '.$value, $replace, $this->statusCode);
}
}
// cookies
//设置cookies
foreach ($this->headers->getCookies() as $cookie) {
header('Set-Cookie: '.$cookie, false, $this->statusCode);
}
// status
// 设置状态码
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
return $this;
}
/**
* Sends content for the current web response.
* 为当前web响应发送内容
* @return $this
*/
public function sendContent()
{
echo $this->content;
return $this;
}
看到这里,大家可能为疑惑,$this->content是什么时候设置的呢?
不知道大家还记不记得上篇的runRoute方法:
/**
* Return the response for the given route.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Routing\Route $route
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function runRoute(Request $request, Route $route)
{
$request->setRouteResolver(function () use ($route) {
return $route;
});
$this->events->dispatch(new RouteMatched($route, $request));
//注意这里的prepareResponse方法
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
我们上篇只是讲到了prepareResponse方法会返回一个Symfony\Component\HttpFoundation\Response类的实例,现在我们来看下prepareResponse方法中都做了什么:
/**
* Create a response instance from the given value.
* 从被给的值创建一个response实例
* @param \Symfony\Component\HttpFoundation\Request $request
* @param mixed $response
* @return \Symfony\Component\HttpFoundation\Response
*/
public function prepareResponse($request, $response)
{
//往下看response方法
return static::toResponse($request, $response);
}
/**
* Static version of prepareResponse.
* prepareResponse的静态版本
* @param \Symfony\Component\HttpFoundation\Request $request
* @param mixed $response
* @return \Symfony\Component\HttpFoundation\Response
*/
public static function toResponse($request, $response)
{
//通过前边的分析,我们知道这里的$response是Symfony\Component\HttpFoundation\Response
//的实例,这样的话这些判断就很容易了
if ($response instanceof Responsable) {
$response = $response->toResponse($request);
}
if ($response instanceof PsrResponseInterface) {
$response = (new HttpFoundationFactory)->createResponse($response);
} elseif ($response instanceof Model && $response->wasRecentlyCreated) {
$response = new JsonResponse($response, 201);
} elseif (! $response instanceof SymfonyResponse &&
($response instanceof Arrayable ||
$response instanceof Jsonable ||
$response instanceof ArrayObject ||
$response instanceof JsonSerializable ||
is_array($response))) {
$response = new JsonResponse($response);
} elseif (! $response instanceof SymfonyResponse) {
//很明显,只有这个条件是符合的,所以我们会执行这个方法,下边我们来看看Illuminate\Http\Response这个类
$response = new Response($response, 200, ['Content-Type' => 'text/html']);
}
if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
$response->setNotModified();
}
return $response->prepare($request);
}
<?php
namespace Illuminate\Http;
use Symfony\Component\HttpFoundation\Response as BaseResponse;
class Response extends BaseResponse{
.......
}
可以看到Illuminate\Http\Response继承了Symfony\Component\HttpFoundation\Response类,现在我们来看下Symfony\Component\HttpFoundation\Response类的构造方法:
/**
* @throws \InvalidArgumentException When the HTTP status code is not valid
*/
public function __construct($content = '', int $status = 200, array $headers = [])
{
$this->headers = new ResponseHeaderBag($headers);
//往下面看setContent方法
$this->setContent($content);
$this->setStatusCode($status);
$this->setProtocolVersion('1.0');
}
/**
* Sets the response content.
* 设置响应内容
* Valid types are strings, numbers, null, and objects that implement a __toString() method.
*
* @param mixed $content Content that can be cast to string
*
* @return $this
*
* @throws \UnexpectedValueException
*/
public function setContent($content)
{
//content如果不是字符串或者数字,则会报错,这也是为什么在控制器方法中返回数组或者对象会报错的原因
if (null !== $content && !\is_string($content) && !is_numeric($content) && !\is_callable([$content, '__toString'])) {
throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', \gettype($content)));
}
//设置了$this->content
$this->content = (string) $content;
return $this;
}
现在,终于是对应上了前边的$this->content,之前提到的sendContent的方法只是把它给输出到了客户端,咱们再来看send方法:
/**
* Sends HTTP headers and content.
*
* @return $this
*/
public function send()
{
$this->sendHeaders();
$this->sendContent();
if (\function_exists('fastcgi_finish_request')) {
//这里如果是fastcgi模式下运行的话,会执行这里,结束此次请求,不然就执行else
fastcgi_finish_request();
} elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
static::closeOutputBuffers(0, true);
}
return $this;
}
到这里send方法就执行完了。
既然请求的结果都已经返回了,那
$kernel->terminate($request, $response);
这行代码是干什么的呢?不要着急往下看,首先terminate的意思是结束,那么依此来看,大概是结束这个应用的意思,现在来看下terminate方法都干了什么:dao
/**
* Call the terminate method on any terminable middleware.
* 在任何terminable中间件上调用terminate方法
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
public function terminate($request, $response)
{
$this->terminateMiddleware($request, $response);
$this->app->terminate();
}
/**
* Call the terminate method on any terminable middleware.
* 在任何terminable中间件上调用terminate方法
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
protected function terminateMiddleware($request, $response)
{
//获取所有的中间件
$middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
$this->gatherRouteMiddleware($request),
$this->middleware
);
foreach ($middlewares as $middleware) {
if (! is_string($middleware)) {
continue;
}
//往下看parseMiddleware方法
[$name] = $this->parseMiddleware($middleware);
//获取中间件实例
$instance = $this->app->make($name);
//如果中间件中定义了terminate方法,则执行此方法
if (method_exists($instance, 'terminate')) {
$instance->terminate($request, $response);
}
}
}
/**
* Parse a middleware string to get the name and parameters.
* 从中间件中解析类名和参数
* @param string $middleware
* @return array
*/
protected function parseMiddleware($middleware)
{
//此处可以看出中间件和参数是根据:分割的
//所以定义路由的时候,如果要给中间件加参数,应该以种形式
//Route::put('post/{id}',function($id)
//{
//
//})->middleware('role:editor');
[$name, $parameters] = array_pad(explode(':', $middleware, 2), 2, []);
if (is_string($parameters)) {
$parameters = explode(',', $parameters);
}
return [$name, $parameters];
}
到这里,框架从启动到请求到响应这一整套流程就算是解读完毕了,通过对流程的解读,对Laravel的生命周期以及之前比较迷茫的知识点基本上也都算是解惑了,当然咱们还有很多没有讲到的,比如事件Event、Mysql、Redis,路由的创建过程等这些常用的东西,我觉得也是有必要了解一下的,既能帮助咱们更好的使用框架,也能学习到一些高明的编码设计,百利而无一害。
解读的过程也是解惑的过程,古语言:“四十不惑”,如果搬到程序员这行来说,程序员的黄金年龄在四十岁之前,所以四十不惑对程序员来说显然不太合理,应该定位到25岁到三十岁这个阶段,我们不仅要会用这些技术,更理解这些技术这样才能走的更远