1. 程式人生 > >Laravel 之道第七章:Laravel 請求物件之 SymfonyRequest

Laravel 之道第七章:Laravel 請求物件之 SymfonyRequest

轉載:https://laravel-china.org/articles/17071/the-seventh-chapter-of-laravel-symfonyrequest-of-laravel-request-objects

從入口檔案開始,先看一段入口檔案的程式碼:

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

相信同僚們,都很清楚這個段程式碼是幹什麼的。套用官方文件的一句話,它就是整個 Laravel 生產 response

物件資料的大黑盒子,材料就是 request 物件。

$kernel 通過上一章,我們知道是 App\Http\Kernel 類的例項物件,此類繼承自 Illuminate\Foundation\Http\Kernel ,看圖:

file

關於 Kernel 類中的 handle 方法

對於這個方法的簡要說明,官方講的很清楚了,具體執行,以後章節會一點點講,非常多呀。。。

官方關於 handle 方法的簡要說明

今天的重點 SymfonyRequest

我們再看一下入口檔案的這段程式碼。

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

大家看到沒 handle 方法的引數是 Illuminate\Http\Request::capture() 靜態方法的返回值

那,我們看一下這個靜態方法。

public static function capture()
{
    static::enableHttpMethodParameterOverride();
    return static::createFromBase
(SymfonyRequest::createFromGlobals()); }

static::enableHttpMethodParameterOverride(); 這行指啟動方法過載,能夠在前端通過 {{method_field('PUT')}} 偽造一個 PUT 或者 DELETE 請求。

第二行的 createFromBase 方法的引數,就是 Symfony 元件有關請求引數處理的返回。

public static function createFromGlobals()
{
    $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;
}

上面就是 SymfonyRequest::createFromGlobals() 靜態方法的程式碼,我們可以看到,通過 createRequestFromFactory 靜態方法處理了 PHP 預定義全域性變數 $_GET 等前端傳送過來的所有資料。這個處理就是例項化當前類,將 PHP 這些預定義變數當做引數,執行建構函式,並返回例項化的物件。

接下對特殊請求方法(PUT、DELETE、PATCH)做初始化時的有關資料操作

最後返回 $request

接下來,我們看一下 createRequestFromFactory 方法

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) {
        $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);
}

self::$requestFactorynull 不執行 if 裡面的程式碼,最後一行,看到沒, new Static(...) ,即例項化當前類,把 PHP 的預定義變數傳入

接下來我們看一下它的建構函式

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);
}

呼叫了 initialize 方法,同樣引數不變。

說一下這幾個引數

$query:PHP 的 $_GET 陣列

$request: PHP 的 $_POST 陣列

$attributes: 其它屬性引數,現在是空陣列,以後可能要往裡添資料

$cookies: PHP 的 $_COOKIE 陣列

$files: PHP 的 $_FILES 陣列

$server: PHP 的 $_SERVER 陣列

好了,我們看一下 intialize 方法:

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;
}

初始化,就是給一堆屬性賦值,除了 filesserverheaders 引數賦予特殊處理物件,其它都是普通引數處理物件(ParameterBag 類的例項)

看一下 ParameterBag 類的建構函式

public function __construct(array $parameters = array())
{
    $this->parameters = $parameters;
}

就是一個簡單屬性賦值

現在看一下 $this->server->getHeaders() 的引數是如何從 server 挑選出 headers

public function getHeaders()
{
    $headers = array();
    $contentHeaders = array('CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true);
    foreach ($this->parameters as $key => $value) {
        if (0 === strpos($key, 'HTTP_')) {
            $headers[substr($key, 5)] = $value;
        }
        // CONTENT_* are not prefixed with HTTP_
        elseif (isset($contentHeaders[$key])) {
            $headers[$key] = $value;
        }
    }
    if (isset($this->parameters['PHP_AUTH_USER'])) {
        $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER'];
        $headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : '';
    } else {
        /*
             * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default
             * For this workaround to work, add these lines to your .htaccess file:
             * RewriteCond %{HTTP:Authorization} ^(.+)$
             * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
             *
             * A sample .htaccess file:
             * RewriteEngine On
             * RewriteCond %{HTTP:Authorization} ^(.+)$
             * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
             * RewriteCond %{REQUEST_FILENAME} !-f
             * RewriteRule ^(.*)$ app.php [QSA,L]
             */
        $authorizationHeader = null;
        if (isset($this->parameters['HTTP_AUTHORIZATION'])) {
            $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION'];
        } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) {
            $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION'];
        }
        if (null !== $authorizationHeader) {
            if (0 === stripos($authorizationHeader, 'basic ')) {
                // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic
                $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2);
                if (2 == count($exploded)) {
                    list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded;
                }
            } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) {
                // In some circumstances PHP_AUTH_DIGEST needs to be set
                $headers['PHP_AUTH_DIGEST'] = $authorizationHeader;
                $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader;
            } elseif (0 === stripos($authorizationHeader, 'bearer ')) {
                /*
                     * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables,
                     *      I'll just set $headers['AUTHORIZATION'] here.
                     *      http://php.net/manual/en/reserved.variables.server.php
                     */
                $headers['AUTHORIZATION'] = $authorizationHeader;
            }
        }
    }
    if (isset($headers['AUTHORIZATION'])) {
        return $headers;
    }
    // PHP_AUTH_USER/PHP_AUTH_PW
    if (isset($headers['PHP_AUTH_USER'])) {
        $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']);
    } elseif (isset($headers['PHP_AUTH_DIGEST'])) {
        $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST'];
    }
    return $headers;
}

首先 foreach 迴圈,從 server 中取出 HTTP_ 開頭和 CONTENT_ 有關的鍵值對,放入 headers 中;然後,取關於授權登入方面的頭資訊,如果 server 中有 PHP_AUTH_USER ,則將 PHP_AUTH_USERPHP_AUTH_PW 取出放入 headers中;如果沒有 PHP_AUTH_USER ,則在 server 中尋找關於 HTTP_AUTHORIZATIONREDIRECT_HTTP_AUTHORIZATIONAUTHORIZATION 的鍵值對;最後對授權內容進行相關字串的變換處理。

最後,再經過 LaravelRequest 類,對生成好的 Symfony Request 物件進行處理;生成了一個 Laravel 類的副本,將 PHP 的預定義變數,賦值到這個副本的屬性中,基本和 Symfony Request 例項化的賦值一樣,之後,對 json 進行相關處理;還有 LaravelRequest 類繼承自 SymfonyRequest 類。

現在,我們到 handle 函式中,看一下生成好的 Request 物件是什麼樣子的:

file

這是 request 物件 的變數詳情:

Illuminate\Http\Request::__set_state(array(
   'json' => NULL,
   'convertedFiles' => NULL,
   'userResolver' => NULL,
   'routeResolver' => NULL,
   'attributes' => 
  Symfony\Component\HttpFoundation\ParameterBag::__set_state(array(
     'parameters' => 
    array (
    ),
  )),
   'request' => 
  Symfony\Component\HttpFoundation\ParameterBag::__set_state(array(
     'parameters' => 
    array (
      'XDEBUG_SESSION_START' => '12084',
    ),
  )),
   'query' => 
  Symfony\Component\HttpFoundation\ParameterBag::__set_state(array(
     'parameters' => 
    array (
      'XDEBUG_SESSION_START' => '12084',
    ),
  )),
   'server' => 
  Symfony\Component\HttpFoundation\ServerBag::__set_state(array(
     'parameters' => 
    array (
      'ALLUSERSPROFILE' => 'C:\\ProgramData',
      'ANDROID_HOME' => 'C:\\Users\\Administrator\\AppData\\Local\\Android\\Sdk',
      'APPDATA' => 'C:\\Users\\Administrator\\AppData\\Roaming',
      'CommonProgramFiles' => 'C:\\Program Files\\Common Files',
      'CommonProgramFiles(x86)' => 'C:\\Program Files (x86)\\Common Files',
      'CommonProgramW6432' => 'C:\\Program Files\\Common Files',
      'COMPUTERNAME' => 'ICOS-20180531MV',
      'ComSpec' => 'C:\\windows\\system32\\cmd.exe',
      'FPS_BROWSER_APP_PROFILE_STRING' => 'Internet Explorer',
      'FPS_BROWSER_USER_PROFILE_STRING' => 'Default',
      'HOMEDRIVE' => 'C:',
      'HOMEPATH' => '\\Users\\Administrator',
      'LOCALAPPDATA' => 'C:\\Users\\Administrator\\AppData\\Local',
      'LOGONSERVER' => '\\\\ICOS-20180531MV',
      'NUMBER_OF_PROCESSORS' => '8',
      'OS' => 'Windows_NT',
      'Path' => 'C:\\Program Files (x86)\\Common Files\\Oracle\\Java\\javapath;C:\\windows\\system32;C:\\windows;C:\\windows\\System32\\Wbem;C:\\windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\NVIDIA Corporation\\PhysX\\Common;C:\\Program Files\\Git\\cmd;D:\\Server\\Shell;C:\\Program Files\\Sublime Text 3;D:\\Server\\Nginx;D:\\Server\\PHP;C:\\Program Files\\Redis\\;C:\\Program Files\\nodejs\\;C:\\Python27;C:\\Users\\Administrator\\AppData\\Local\\Android\\Sdk\\platform-tools;D:\\Server\\MySQL\\bin;C:\\Users\\Administrator\\AppData\\Local\\Microsoft\\WindowsApps;C:\\Users\\Administrator\\AppData\\Roaming\\npm',
      'PATHEXT' => '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL',
      'PROCESSOR_ARCHITECTURE' => 'AMD64',
      'PROCESSOR_IDENTIFIER' => 'Intel64 Family 6 Model 142 Stepping 10, GenuineIntel',
      'PROCESSOR_LEVEL' => '6',
      'PROCESSOR_REVISION' => '8e0a',
      'ProgramData' => 'C:\\ProgramData',
      'ProgramFiles' => 'C:\\Program Files',
      'ProgramFiles(x86)' => 'C:\\Program Files (x86)',
      'ProgramW6432' => 'C:\\Program Files',
      'PROMPT' => '$P$G',
      'PSModulePath' => 'C:\\Users\\Administrator\\Documents\\WindowsPowerShell\\Modules;C:\\Program Files\\WindowsPowerShell\\Modules;C:\\windows\\system32\\WindowsPowerShell\\v1.0\\Modules',
      'PUBLIC' => 'C:\\Users\\Public',
      'SESSIONNAME' => 'Console',
      'SystemDrive' => 'C:',
      'SystemRoot' => 'C:\\windows',
      'TEMP' => 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp',
      'TMP' => 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp',
      'USERDOMAIN' => 'ICOS-20180531MV',
      'USERDOMAIN_ROAMINGPROFILE' => 'ICOS-20180531MV',
      'USERNAME' => 'Administrator',
      'USERPROFILE'