首頁  >  文章  >  後端開發  >  Laravel核心解讀Request

Laravel核心解讀Request

不言
不言原創
2018-07-06 14:54:374110瀏覽

這篇文章主要介紹了關於Laravel核心解讀Request,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下

Request

很多框架都會將來自客戶端的請求抽象化成類別方便應用程式使用,在Laravel中也不例外。 Illuminate\Http\Request類別在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(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    //创建request对象
    $request = Illuminate\Http\Request::capture()
);

客戶端的HTTP請求是Illuminate\Http\Request類別的物件

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類別的實例的。

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可以透過命令列解釋器來啟動,例如:

php -S localhost:8000 -t htdocs

-S 9581b3ba3b9ee3c707d01b5722440004:298c9bd6ad6e8c821dc63aa0473d6209 Run with built-in web server.
-t ea23c61ebf6b70b26359136364366495     Specify document root ea23c61ebf6b70b26359136364366495 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 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已經提供的方法。

以上就是本文的全部內容,希望對大家的學習有所幫助,更多相關內容請關注PHP中文網!

相關推薦:

#

以上是Laravel核心解讀Request的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn