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
,看圖:
關於 Kernel
類中的 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::$requestFactory
為 null
不執行 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;
}
初始化,就是給一堆屬性賦值,除了 files
、server
、headers
引數賦予特殊處理物件,其它都是普通引數處理物件(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_USER
和 PHP_AUTH_PW
取出放入 headers
中;如果沒有 PHP_AUTH_USER
,則在 server
中尋找關於 HTTP_AUTHORIZATION
、REDIRECT_HTTP_AUTHORIZATION
、AUTHORIZATION
的鍵值對;最後對授權內容進行相關字串的變換處理。
最後,再經過 Laravel
的 Request
類,對生成好的 Symfony Request
物件進行處理;生成了一個 Laravel
類的副本,將 PHP
的預定義變數,賦值到這個副本的屬性中,基本和 Symfony Request
例項化的賦值一樣,之後,對 json
進行相關處理;還有 Laravel
的 Request
類繼承自 Symfony
的 Request
類。
現在,我們到 handle
函式中,看一下生成好的 Request
物件是什麼樣子的:
這是 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'