生命周期 3--request 对象创建过程浅析

首先,$app对象$kernel对象已经创建完毕。

接下来,是创建即将被$kernel对象处理的$request对象,然后返回$repsonse对象.

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

创建$request对象

  • 首先调用Illuminate\Http\Request的capture()方法.

    public static function capture()
    {
        static::enableHttpMethodParameterOverride();    //step1
    
        return static::createFromBase(SymfonyRequest::createFromGlobals()); //step2-step3
    }

step1,设置“可以进行表单方法欺骗”

    //如果设置为true,那么对于method为POST的表单,就可以通过_method='PUT'或_method='DELETE',_method='PATCH'来模拟这些类型的请求。
    public static function enableHttpMethodParameterOverride()
    {
        self::$httpMethodParameterOverride = true;
    }

step2, 调用Symfony框架的Request类的createFromGlobals方法,来创建一个SymfonyRequest对象。


    public static function createFromGlobals()
    {
        $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER); //step2.1

        if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
            && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
        ) { //step2.2
            parse_str($request->getContent(), $data);   //step2.2.1-step2.2.2
            $request->request = new ParameterBag($data);    //step2.2.3
        }

        return $request;   
    }

step2.1, 调用Symfony框架的Request类的createRequestFromFactory方法,使用列出的全局变量来创建一个SymfonyRequest对象。

    private static function createRequestFromFactory(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
    {
        if (self::$requestFactory) {    //step2.1.1
            $request = \call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content);

            if (!$request instanceof self) {
                throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.');
            }

            return $request;
        }

        return new static($query, $request, $attributes, $cookies, $files, $server, $content);  //step2.1.2
    }

step2.1.1, 判断是否有自定义的request工厂

  • 如果有,就调用工厂方法来创建SymfonyRequest对象。

step2.1.2, 如果没有,就new一个SymfonyRequest对象,并将global 变量传进去初始化。

  • 初始化SymfonyRequest对象也就是执行initialize方法,用来设置新创建对象的属性。
    public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
    {
        $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);  //step2.1.2.1
    }
  • initialize方法中无非就是各种对global变量的过滤处理,涉及到ParameterBag,FileBag,ServerBag,HeaderBag。
    • ParameterBag是一个键值对的容器。
    • FileBag是ParameterBag的子类,添加了一些对上传文件特有的操作。
    • ServerBag也是ParameterBag的子类,添加了一个getHeaders方法,用来获取HTTP头信息。
    • HeaderBag是一个HTTP头信息的容器,里面有关于HTTP头信息的各种方法。

step2.2, 对PUT/DELETE/PATCH路由的处理。

  • 如果前面创建的SymfonyRequest对象的headers中CONTENT_TYPE==='application/x-www-form-urlencoded',并且REQUEST_METHOD属于PUT/DELETE/PATCH的任意一种,那么就会进行下面的处理。

    step2.2.1 获取SymfonyRequest对象的body content

        $currentContentIsResource = \is_resource($this->content);
    
        if (true === $asResource) { //是否需要作为资源返回
            if ($currentContentIsResource) {
                rewind($this->content);
    
                return $this->content;
            }
    
            // Content passed in parameter (test)
            if (\is_string($this->content)) {
                $resource = fopen('php://temp', 'r+');
                fwrite($resource, $this->content);
                rewind($resource);
    
                return $resource;
            }
    
            $this->content = false;
    
            return fopen('php://input', 'rb');
        }
    
        if ($currentContentIsResource) {    //如果传入内容是资源,那么将资源转换为字符串返回
            rewind($this->content);
    
            return stream_get_contents($this->content);
        }
    
        if (null === $this->content || false === $this->content) {  //如果content为null或者false,那么从`php://input`去获取字符串。
            $this->content = file_get_contents('php://input');
        }
    
        return $this->content;
  • $asResource指定是否要作为资源返回,目前是不需要。那么就是作为字符串返回。
  • 如果传入内容是资源,那么将资源转换为字符串返回。
  • 如果content为null或者false,那么从php://input去获取字符串。
  • 如果上述条件都没有match,就返回原字符串(一般格式为:avatar_image_id=1&name=hustnzj&email=12345%40qq.com&introduction=hehe)

step2.2.2 将上述body content转换为$data数组

step2.2.3 以$data数组创建一个ParameterBag对象,并赋值给SymfonyRequest对象的request属性。

step3 调用\Illuminate\Http\Request::createFromBase方法,以SymfonyRequest对象为模板创建一个Illuminate request对象。

    public static function createFromBase(SymfonyRequest $request)
    {
        if ($request instanceof static) { //step3.1
            return $request;
        }

        $content = $request->content;   //step3.2

        $request = (new static)->duplicate( //step3.3
            $request->query->all(), $request->request->all(), $request->attributes->all(),
            $request->cookies->all(), $request->files->all(), $request->server->all()
        );

        $request->content = $content;   //step3.4

        $request->request = $request->getInputSource(); //step3.5

        return $request;
    }

step3.1 判断传入的request对象是不是Illuminate request对象

  • 如果已经是Illuminate request对象,那么就不用下面的操作了,直接返回。

step3.2 保存SymfonyRequest对象的content到$content变量中备用。

step3.3 new一个IlluminateRequest对象

  • 创建IlluminateRequest对象,跟step2.1.2不同的是,没有任何global变量传入,是一个"干净"的对象,
  • 将原来SymfonyRequest对象中的各种Bag传入IlluminateRequest对象的duplicate方法,并对files数组进行了空值处理。
  • \Symfony\Component\HttpFoundation\Request::duplicate方法中,对现有IlluminateRequest对象进行了clone,然后将传入的各种bag参数设置到新clone的IlluminateRequest对象上。

step3.4 用step3.2备份的$content重新设置一下当前IlluminateRequest对象的content属性。

step3.5 调用\Illuminate\Http\Request::getInputSource方法,获取当前request的input source,并设置当前IlluminateRequest对象的request参数包。

    protected function getInputSource()
    {
        if ($this->isJson()) {  //step 3.5.1
            return $this->json();
        }

        return in_array($this->getRealMethod(), ['GET', 'HEAD']) ? $this->query : $this->request;   //step 3.5.2
    }

step3.5.1 判断当前request是否在发送json字符串

    public function json($key = null, $default = null)
    {
        if (! isset($this->json)) {
            $this->json = new ParameterBag((array) json_decode($this->getContent(), true));
        }

        if (is_null($key)) {
            return $this->json;
        }

        return data_get($this->json->all(), $key, $default);
    }
  • 如果是,就将发送的json字符串打散为数组,然后放在当前request对象的json属性参数包中。
  • 如果已经有设置过,就直接返回。

step3.5.2 如果当前request的方法是GET/HEAD就返回query参数包,否则返回request参数包。

  • 通过当前request对象的server参数包获取到当前的request_method
    public function getRealMethod()
    {
        return strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
    }

综上所述,这个过程就是先启动表单方法重载,然后使用\Symfony\Component\HttpFoundation\RequestcreateFromGlobals方法创建一个SymfonyRequest对象,然后创建一个IlluminateRequest对象,再将SymfonyRequest对象的各种属性值经过各种处理后传过去,最后返回IlluminateRequest对象。

参考

本作品采用《CC 协议》,转载必须注明作者和本文链接
日拱一卒
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 3

排版有点乱

6年前 评论

@lovecn 感谢指出,有空我会再优化一下

6年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
92
粉丝
87
喜欢
152
收藏
121
排名:72
访问:11.3 万
私信
所有博文
社区赞助商