资讯专栏INFORMATION COLUMN

Laravel核心解读 -- Request

K_B_Z / 3102人阅读

摘要:根据提供的超级全局数组来创建实例上面的代码有一处需要额外解释一下,自开始内建的可以通过命令行解释器来启动,例如但是内建有一个是将和这两个请求首部存储到了和中,为了统一内建服务器和真正的中的请求首部字段所以在这里做了特殊处理。

Request

很多框架都会将来自客户端的请求抽象成类方便应用程序使用,在Laravel中也不例外。IlluminateHttpRequest类在Laravel框架中就是对客户端请求的抽象,它是构建在Symfony框架提供的Request组件基础之上的。今天这篇文章就简单来看看Laravel是怎么创建请求Request对象的,而关于Request对象为应用提供的能力我并不会过多去说,在我讲完创建过程后你也就知道去源码哪里找Request对象提供的方法了,网上有些速查表列举了一些Request提供的方法不过不够全并且有的也没有解释,所以我还是推荐在开发中如果好奇Request是否已经实现了你想要的能力时去Request的源码里看下有没有提供对应的方法,方法注释里都清楚地标明了每个方法的执行结果。下面让我们进入正题吧。

创建Request对象

我们可以在Laravel应用程序的index.php文件中看到,在Laravel应用程序正式启动完成前Request对象就已经被创建好了:

//public/index.php
$app = require_once __DIR__."/../bootstrap/app.php";

$kernel = $app->make(IlluminateContractsHttpKernel::class);

$response = $kernel->handle(
    //创建request对象
    $request = IlluminateHttpRequest::capture()
);

客户端的HTTP请求是IlluminateHttpRequest类的对象

class Request extends SymfonyRequest implements Arrayable, ArrayAccess
{
    //新建Request实例
    public static function capture()
    {
        static::enableHttpMethodParameterOverride();

        return static::createFromBase(SymfonyRequest::createFromGlobals());
    }
}

通过IlluminateHttpRequest类的源码可以看到它是继承自Symfony Request类的,所以IlluminateHttpRequest类中实现的很多功能都是以Symfony Reques提供的功能为基础来实现的。上面的代码就可以看到capture方法新建Request对象时也是依赖于Symfony Request类的实例的。

namespace SymfonyComponentHttpFoundation;
class Request
{
    /**
     * 根据PHP提供的超级全局数组来创建Smyfony Request实例
     *
     * @return static
     */
    public static function createFromGlobals()
    {
        // With the php"s bug #66606, the php"s built-in web server
        // stores the Content-Type and Content-Length header values in
        // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields.
        $server = $_SERVER;
        if ("cli-server" === PHP_SAPI) {
            if (array_key_exists("HTTP_CONTENT_LENGTH", $_SERVER)) {
                $server["CONTENT_LENGTH"] = $_SERVER["HTTP_CONTENT_LENGTH"];
            }
            if (array_key_exists("HTTP_CONTENT_TYPE", $_SERVER)) {
                $server["CONTENT_TYPE"] = $_SERVER["HTTP_CONTENT_TYPE"];
            }
        }

        $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server);

        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"))
        ) {
            parse_str($request->getContent(), $data);
            $request->request = new ParameterBag($data);
        }

        return $request;
    }
    
}

上面的代码有一处需要额外解释一下,自PHP5.4开始PHP内建的builtin web server可以通过命令行解释器来启动,例如:

php -S localhost:8000 -t htdocs

-S : Run with built-in web server.
-t      Specify document root  for built-in web server.

但是内建web server有一个bug是将CONTENT_LENGTHCONTENT_TYPE这两个请求首部存储到了HTTP_CONTENT_LENGTHHTTP_CONTENT_TYPE中,为了统一内建服务器和真正的server中的请求首部字段所以在这里做了特殊处理。

Symfony Request 实例的创建是通过PHP中的超级全局数组来创建的,这些超级全局数组有$_GET$_POST$_COOKIE$_FILES$_SERVER涵盖了PHP中所有与HTTP请求相关的超级全局数组,创建Symfony Request实例时会根据这些全局数组创建Symfony Package里提供的ParamterBag ServerBag FileBag HeaderBag实例,这些Bag都是Symfony提供地针对不同HTTP组成部分的访问和设置API, 关于Symfony提供的ParamterBag这些实例有兴趣的读者自己去源码里看看吧,这里就不多说了。

class Request
{

    /**
     * @param array                $query      The GET parameters
     * @param array                $request    The POST parameters
     * @param array                $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
     * @param array                $cookies    The COOKIE parameters
     * @param array                $files      The FILES parameters
     * @param array                $server     The SERVER parameters
     * @param string|resource|null $content    The raw body data
     */
    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);
    }
    
    public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
    {
        $this->request = new ParameterBag($request);
        $this->query = new ParameterBag($query);
        $this->attributes = new ParameterBag($attributes);
        $this->cookies = new ParameterBag($cookies);
        $this->files = new FileBag($files);
        $this->server = new ServerBag($server);
        $this->headers = new HeaderBag($this->server->getHeaders());

        $this->content = $content;
        $this->languages = null;
        $this->charsets = null;
        $this->encodings = null;
        $this->acceptableContentTypes = null;
        $this->pathInfo = null;
        $this->requestUri = null;
        $this->baseUrl = null;
        $this->basePath = null;
        $this->method = null;
        $this->format = null;
    }
    
}

可以看到Symfony Request类除了上边说到的那几个,还有很多属性,这些属性在一起构成了对HTTP请求完整的抽象,我们可以通过实例属性方便地访问MethodCharset等这些HTTP请求的属性。

拿到Symfony Request实例后, Laravel会克隆这个实例并重设其中的一些属性:

namespace IlluminateHttp;
class Request extends ....
{
    //在Symfony request instance的基础上创建Request实例
    public static function createFromBase(SymfonyRequest $request)
    {
        if ($request instanceof static) {
            return $request;
        }

        $content = $request->content;

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

        $request->content = $content;

        $request->request = $request->getInputSource();

        return $request;
    }
    
    public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
    {
        return parent::duplicate($query, $request, $attributes, $cookies, $this->filterFiles($files), $server);
    }
}
    //Symfony Request中的 duplicate方法
    public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
    {
        $dup = clone $this;
        if (null !== $query) {
            $dup->query = new ParameterBag($query);
        }
        if (null !== $request) {
            $dup->request = new ParameterBag($request);
        }
        if (null !== $attributes) {
            $dup->attributes = new ParameterBag($attributes);
        }
        if (null !== $cookies) {
            $dup->cookies = new ParameterBag($cookies);
        }
        if (null !== $files) {
            $dup->files = new FileBag($files);
        }
        if (null !== $server) {
            $dup->server = new ServerBag($server);
            $dup->headers = new HeaderBag($dup->server->getHeaders());
        }
        $dup->languages = null;
        $dup->charsets = null;
        $dup->encodings = null;
        $dup->acceptableContentTypes = null;
        $dup->pathInfo = null;
        $dup->requestUri = null;
        $dup->baseUrl = null;
        $dup->basePath = null;
        $dup->method = null;
        $dup->format = null;

        if (!$dup->get("_format") && $this->get("_format")) {
            $dup->attributes->set("_format", $this->get("_format"));
        }

        if (!$dup->getRequestFormat(null)) {
            $dup->setRequestFormat($this->getRequestFormat(null));
        }

        return $dup;
    }

Request对象创建好后在Laravel应用中我们就能方便的应用它提供的能力了,在使用Request对象时如果你不知道它是否实现了你想要的功能,很简单直接去IlluminateHttpRequest的源码文件里查看就好了,所有方法都列在了这个源码文件里,比如:

/**
 * Get the full URL for the request.
 * 获取请求的URL(包含host, 不包括query string)
 *
 * @return string
 */
public function fullUrl()
{
    $query = $this->getQueryString();

    $question = $this->getBaseUrl().$this->getPathInfo() == "/" ? "/?" : "?";

    return $query ? $this->url().$question.$query : $this->url();
}

/**
 * Get the full URL for the request with the added query string parameters.
 * 获取包括了query string 的完整URL
 *
 * @param  array  $query
 * @return string
 */
public function fullUrlWithQuery(array $query)
{
    $question = $this->getBaseUrl().$this->getPathInfo() == "/" ? "/?" : "?";

    return count($this->query()) > 0
        ? $this->url().$question.http_build_query(array_merge($this->query(), $query))
        : $this->fullUrl().$question.http_build_query($query);
}
Request经过的驿站

创建完Request对象后, Laravel的Http Kernel会接着往下执行:加载服务提供器引导Laravel应用、启动应用、让Request经过基础的中间件、通过Router匹配查找Request对应的路由、执行匹配到的路由、Request经过路由上到中间件到达控制器方法。

总结

随着Request最终到达对应的控制器方法后它的使命基本上也就完成了, 在控制器方法里从Request中获取输入参数然后执行应用的某一业务逻辑获得结果,结果会被转化成Response响应对象返回给发起请求的客户端。

这篇文章主要梳理了Laravel中Request对象,主要是想让大家知道如何去查找Laravel中Request现有提供了哪些能力供我们使用避免我们在业务代码里重新造轮子去实现Request已经提供的方法。

本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/30804.html

相关文章

  • Laravel核心解读 -- Response

    摘要:设置生成对象后就要执行对象的方法了,该方法定义在类中,其主要目的是对进行微调使其能够遵从协议。最后会把完整的响应发送给客户端。本文已经收录在系列文章源码学习里,欢迎访问阅读。 Response 前面两节我们分别讲了Laravel的控制器和Request对象,在讲Request对象的那一节我们看了Request对象是如何被创建出来的以及它支持的方法都定义在哪里,讲控制器时我们详细地描述了...

    TigerChain 评论0 收藏0
  • Laravel核心解读--控制器

    摘要:下面是刚才说的这些步骤对应的核心代码收集路由和控制器里应用的中间件我们在前面的文章里已经详细的解释过中间件和路由的原理了,接下来就看看当请求最终找到了路由对应的控制器方法后是如何为控制器方法注入正确的参数并调用控制器方法的。 控制器 控制器能够将相关的请求处理逻辑组成一个单独的类, 通过前面的路由和中间件两个章节我们多次强调Laravel应用的请求在进入应用后首现会通过Http Ker...

    fxp 评论0 收藏0
  • Laravel核心解读--用户认证系统的实现细节

    摘要:通过装载看守器和用户提供器装载看守器和用户提供器用到的方法比较多,用文字描述不太清楚,我们通过注解这个过程中用到的方法来看具体的实现细节。 用户认证系统的实现细节 上一节我们介绍来Laravel Auth系统的基础知识,说了他的核心组件都有哪些构成,这一节我们会专注Laravel Auth系统的实现细节,主要关注Auth也就是AuthManager是如何装载认证用的看守器(Guard)...

    NicolasHe 评论0 收藏0
  • Laravel核心解读--中间件(Middleware)

    摘要:解析出后将进入应用的请求对象传递给的方法,在方法负责处理流入应用的请求对象并返回响应对象。携带了本次迭代的值。通过这种方式让请求对象依次流过了要通过的中间件,达到目的地的方法。 中间件(Middleware)在Laravel中起着过滤进入应用的HTTP请求对象(Request)和完善离开应用的HTTP响应对象(Reponse)的作用, 而且可以通过应用多个中间件来层层过滤请求、逐步完善...

    enda 评论0 收藏0
  • Laravel核心解读--异常处理

    摘要:请求未通过的验证时会抛出此异常。异常处理是非常重要但又容易让开发者忽略的功能,这篇文章简单解释了内部异常处理的机制以及扩展异常处理的方式方法。 异常处理是编程中十分重要但也最容易被人忽视的语言特性,它为开发者提供了处理程序运行时错误的机制,对于程序设计来说正确的异常处理能够防止泄露程序自身细节给用户,给开发者提供完整的错误回溯堆栈,同时也能提高程序的健壮性。 这篇文章我们来简单梳理一下...

    includecmath 评论0 收藏0

发表评论

0条评论

K_B_Z

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<