Laravel核心代碼學習 -- 請求Request

Request

不少框架都會未來自客戶端的請求抽象成類方便應用程序使用,在Laravel中也不例外。Illuminate\Http\Request類在Laravel框架中就是對客戶端請求的抽象,它是構建在Symfony框架提供的Request組件基礎之上的。今天這篇文章就簡單來看看Laravel是怎麼建立請求Request對象的,而關於Request對象爲應用提供的能力我並不會過多去說,在我講完建立過程後你也就知道去源碼哪裏找Request對象提供的方法了,網上有些速查表列舉了一些Request提供的方法不過不夠全而且有的也沒有解釋,因此我仍是推薦在開發中若是好奇Request是否已經實現了你想要的能力時去Request的源碼裏看下有沒有提供對應的方法,方法註釋裏都清楚地標明瞭每一個方法的執行結果。下面讓咱們進入正題吧。php

建立Request對象

咱們能夠在Laravel應用程序的index.php文件中看到,在Laravel應用程序正式啓動完成前Request對象就已經被建立好了:git

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

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    //建立request對象
    $request = Illuminate\Http\Request::capture()
);
複製代碼

客戶端的HTTP請求是Illuminate\Http\Request類的對象github

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

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

複製代碼

經過Illuminate\Http\Request類的源碼能夠看到它是繼承自Symfony Request類的,因此Illuminate\Http\Request類中實現的不少功能都是以Symfony Reques提供的功能爲基礎來實現的。上面的代碼就能夠看到capture方法新建Request對象時也是依賴於Symfony Request類的實例的。web

namespace Symfony\Component\HttpFoundation;
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能夠經過命令行解釋器來啓動,例如:bootstrap

php -S localhost:8000 -t htdocs數組

-S <addr>:<port> Run with built-in web server.
-t <docroot>     Specify document root <docroot> for built-in web server.
複製代碼

可是內建web server有一個bug是將CONTENT_LENGTHCONTENT_TYPE這兩個請求首部存儲到了HTTP_CONTENT_LENGTHHTTP_CONTENT_TYPE中,爲了統一內建服務器和真正的server中的請求首部字段因此在這裏作了特殊處理。bash

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請求的屬性。cookie

拿到Symfony Request實例後, Laravel會克隆這個實例並重設其中的一些屬性:app

namespace Illuminate\Http;
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對象時若是你不知道它是否實現了你想要的功能,很簡單直接去Illuminate\Http\Request的源碼文件裏查看就行了,全部方法都列在了這個源碼文件裏,好比:

/**
 * 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源碼學習裏,歡迎訪問閱讀。

相關文章
相關標籤/搜索