Laravel源码剖析之请求的处理下(六)

建议先看前几篇文章,不然可能会看不懂,这是我的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岁到三十岁这个阶段,我们不仅要会用这些技术,更理解这些技术这样才能走的更远

猜你喜欢

转载自blog.csdn.net/Attitude_do_it/article/details/121554000