<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                前面?[_請求(Reqeust)_](http://www.digpage.com/request.html#request)?部分我們講了用戶請求的基礎知識和命令行應用的Request,接下來繼續講Web應用的Request。 Web應用Request由?yii\web\Request?實現,這個類的代碼將近1400行,主要是一些功能的封裝罷了, 原理上沒有很復雜的東西。只是涉及到許多HTTP的有關知識,讀者朋友們可以自行查看相關的規范文檔, 如?[HTTP 1.1 協議](https://tools.ietf.org/html/rfc2616)?,?[CGI 1.1 規范](https://tools.ietf.org/html/rfc3875.html)?等。 同時,Yii大量引用了?$_SERVER?, 具體可以查看?[PHP文檔關于$_SERVER的內容](http://php.net/manual/en/reserved.variables.server.php)?, 此外,還涉及到PHP運行于不同的環境和模式下的一些細微差別。 這些內容比較細節,不影響大局,但是很影響理解,不過沒關系,我們在涉及到的時候,會點一點。 ## 請求的方法[](http://www.digpage.com/web_request.html#id1 "Permalink to this headline") 根據?[HTTP 1.1 協議](https://tools.ietf.org/html/rfc2616)?,HTTP的請求可以有:GET, POST, PUT等8種方法 (Request Method)。除了用不到的 CONNECT 外,Yii支持全部的HTTP請求方法。 要獲取當前用戶請求的方法,可以使用?yii\web\Request::getMethod() ~~~ // 返回當前請求的方法,請留意方法名稱是大小寫敏感的,按規范應轉換為大寫字母 public function getMethod() { // $this->methodParam 默認值為 '_method' // 如果指定 $_POST['_method'] ,表示使用POST請求來模擬其他方法的請求。 // 此時 $_POST['_method'] 即為所模擬的請求類型。 if (isset($_POST[$this->methodParam])) { return strtoupper($_POST[$this->methodParam]); // 或者使用 $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] 的值作為方法名。 } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { return strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); // 或者使用 $_SERVER['REQUEST_METHOD'] 作為方法名,未指定時,默認為 GET 方法 } else { return isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET'; } } ~~~ 這個方法使用了3種方法來獲取當前用戶的請求,優先級從高到低依次為: * 當使用POST請求來模擬其他請求時,以?$_POST['_method']?作為當前請求的方法; * 否則,如果存在?X_HTTP_METHOD_OVERRIDE?HTTP頭時,以該HTTP頭所指定的方法作為請求方法, 如?X-HTTP-Method-Override:?PUT?表示該請求所要執行的是 PUT 方法; * 如果?X_HTTP_METHOD_OVERRIDE?不存在,則以?REQUEST_METHOD?的值作為當前請求的方法。 如果連REQUEST_METHOD?也不存在,則視該請求是一個 GET 請求。 前面兩種方法,主要是針對一些只支持GET和POST等有限方法的User Agent而設計的。 其中第一種方法是從Ruby on Rails中借鑒過來的, 通過在發送POST請求時,加入一個$_POST['_method']?的隱藏字段,來表示所要模擬的方法, 如PUT,DELETE等。這樣,就可以使得這些功能有限的User Agent也可以正常與服務器交互。 這種方法勝在簡便,隨手就來。 第二種方法則是使用?X_HTTP_METHOD_OVERRIDE?HTTP頭的辦法來指定所使用的請求類型。 這種方法勝在直接明了,約定俗成,更為規范、合理。 至于?REQUEST_METHOD?是?[CGI 1.1 規范](https://tools.ietf.org/html/rfc3875.html)?所定義的環境變量, 專門用來表明當前請求方法的。上面的代碼只是在未指定時默認為GET請求罷了。 當然,我們在開發過程中,其實并不怎么在乎當前的用戶請求是什么類型的請求,我們更在乎是不是某一類型的請求。 比如,對于同一個URL地址?http://api.digpage.com/post/123?, 如果是正常的GET請求,應該是查看編號為123的文章的意思。 但是如果是一個DELETE請求,則是表示刪除編號為123的文章的意思。我們在開發中,很可能就會這么寫: ~~~ if ($app->request->isDelete()){ $post->delete(); } else { $post->view(); } ~~~ 上面的代碼只是一個示意,與實際編碼是有一定出入的,主要看判斷分支的用法。 就是判斷請求是否是某一特定類型的請求。這些判斷在實際開發中,是很常用的。 于是Yii為我們封裝了許多方法專門用于執行這些判斷: * getIsAjax()?是否是AJAX請求,這其實不是HTTP請求方法,但是實際使用上,這個是用得最多的。 * getIsDelete()?是否是DELETE請求 * getIsFlash()?是否是Adobe Flash 或 Adobe Flex 發出的請求,這其實也不是HTTP請求方法。 * getIsGet()?是否是一個GET請求 * getIsHead()?是否是一個HEAD請求 * getIsOptions()?是否是一個OPTIONS請求 * getIsPatch()?是否是PATCH請求 * getIsPjax()?是否是一個PJAX請求,這也并非是HTTP請求方法。 * getIsPost()?是否是一個POST請求 * getIsPut()?是否是一個PUT請求 上面10個方法請留意其中有3個并未是HTTP請求方法,主要是用于特定HTTP請求類型(AJAX、Flash、PJAX)的判斷。 除了這3個之外的其余7個方法,正好對應于HTTP 1.1 協議定義的7個方法。 而CONNECT方法由于Web開發在用不到,主要用于HTTP代理, 因此,Yii也就沒有為其設計一個所謂的?isConnect()?了,這是無用功。 上面的10個方法,再加一開始說的?getMehtod()?一共是11個方法,按照我們在?[_屬性(Property)_](http://www.digpage.com/property.html#property)?部分所說的, 這相當于定義了11個只讀屬性。我們以其中幾個為例,看看具體實現: ~~~ // 這個SO EASY,啥也不說了,Yii實現的7個HTTP方法都是這個路子。 public function getIsOptions() { // 注意在getMethod()時,輸出的是全部大寫的字符串 return $this->getMethod() === 'OPTIONS'; } // AJAX請求是通過 X_REQUESTED_WITH 消息頭來判斷的 public function getIsAjax() { // 注意這里的XMLHttpRequest沒有全部大寫 return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest'; } // PJAX請求是AJAX請求的一種,增加了X_PJAX消息頭的定義 public function getIsPjax() { return $this->getIsAjax() && !empty($_SERVER['HTTP_X_PJAX']); } // HTTP_USER_AGENT消息頭中包含 'Shockwave' 或 'Flash' 字眼的(不區分大小寫), // 就認為是FLASH請求 public function getIsFlash() { return isset($_SERVER['HTTP_USER_AGENT']) && (stripos($_SERVER['HTTP_USER_AGENT'], 'Shockwave') !== false || stripos($_SERVER['HTTP_USER_AGENT'], 'Flash') !== false); } ~~~ 上面提到的AJAX、PJAX、FLASH請求比較特殊,并非是HTTP協議所規定的請求類型,但是在實現中是會使用到的。 比如,對于一個請求,在非AJAX時,需要整個頁面返回給客戶端,而在AJAX請求時,只需要返回頁面片段即可。 這些特殊請求是通過特殊的消息頭實現的,具體的可以自行搜索相關的定義和規范。 至于那7個HTTP方法的判斷,擺明了是同一個路子,換瓶不換酒,?getMethod()?前人栽樹,他們后人乘涼。 ## 請求的參數[](http://www.digpage.com/web_request.html#id4 "Permalink to this headline") 在實際開發中,開發者如果需要引用request,最常見的情況是為了獲取請求參數,以便作相應處理。 PHP有眾所周知的?$_GET?和?$_POST?等。相應地,Yii提供了一系列的方法用于獲取請求參數: ~~~ // 用于獲取GET參數,可以指定參數名和默認值 public function get($name = null, $defaultValue = null) { if ($name === null) { return $this->getQueryParams(); } else { return $this->getQueryParam($name, $defaultValue); } } // 用于獲取所有的GET參數 // 所有的GET參數保存在 $_GET 或 $this->_queryParams 中。 public function getQueryParams() { if ($this->_queryParams === null) { // 請留意這里并未使用 $this->_queryParams = $_GET 進行緩存。 // 說明一旦指定了 $_queryParams 則 $_GET 會失效。 return $_GET; } return $this->_queryParams; } // 根據參數名獲取單一的GET參數,不存在時,返回指定的默認值 public function getQueryParam($name, $defaultValue = null) { $params = $this->getQueryParams(); return isset($params[$name]) ? $params[$name] : $defaultValue; } // 類以于get(),用于獲取POST參數,也可以指定參數名和默認值 public function post($name = null, $defaultValue = null) { if ($name === null) { return $this->getBodyParams(); } else { return $this->getBodyParam($name, $defaultValue); } } // 根據參數名獲取單一的POST參數,不存在時,返回指定的默認值 public function getBodyParam($name, $defaultValue = null) { $params = $this->getBodyParams(); return isset($params[$name]) ? $params[$name] : $defaultValue; } // 獲取所有POST參數,所有POST參數保存在 $this->_bodyParams 中 public function getBodyParams() { if ($this->_bodyParams === null) { // 如果是使用 POST 請求模擬其他請求的 if (isset($_POST[$this->methodParam])) { $this->_bodyParams = $_POST; // 將 $_POST['_method'] 刪掉,剩余的$_POST就是了 unset($this->_bodyParams[$this->methodParam]); return $this->_bodyParams; } // 獲取Content Type // 對于 'application/json; charset=UTF-8',得到的是 'application/json' $contentType = $this->getContentType(); if (($pos = strpos($contentType, ';')) !== false) { $contentType = substr($contentType, 0, $pos); } // 根據Content Type 選擇相應的解析器對請求體進行解析 if (isset($this->parsers[$contentType])) { // 創建解析器實例 $parser = Yii::createObject($this->parsers[$contentType]); if (!($parser instanceof RequestParserInterface)) { throw new InvalidConfigException( "The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface."); } // 將請求體解析到 $this->_bodyParams $this->_bodyParams = $parser->parse($this->getRawBody(), $contentType); // 如果沒有與Content Type對應的解析器,使用通用解析器 } elseif (isset($this->parsers['*'])) { $parser = Yii::createObject($this->parsers['*']); if (!($parser instanceof RequestParserInterface)) { throw new InvalidConfigException( "The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface."); } $this->_bodyParams = $parser->parse($this->getRawBody(), $contentType); // 連通用解析器也沒有 // 看看是不是POST請求,如果是,PHP已經將請求參數放到$_POST中了,直接用就OK了 } elseif ($this->getMethod() === 'POST') { $this->_bodyParams = $_POST; // 以上情況都不是,那就使用PHP的 mb_parse_str() 進行解析 } else { $this->_bodyParams = []; mb_parse_str($this->getRawBody(), $this->_bodyParams); } } return $this->_bodyParams; } ~~~ 在上面的代碼中,將所有的請求參數劃分為兩類, 一類是包含在URL中的,稱為查詢參數(Query Parameter),或GET參數。 另一類是包含在請求體中的,需要根據請求體的內容類型(Content Type)進行解析,稱為POST參數。 其中,?get()?,?getQueryParams()?和?getQueryParam()?用于獲取查詢參數: * get()?用于獲取GET參數,可以指定所要獲取的特定參數的參數名,在這個參數名不存在時,可以指定默認值。 當不指定參數名時,獲取所有的GET參數。 具體功能是由下面2個函數來實現的。 * getQueryParams()?用于獲取所有的GET參數。 這些參數的內容,保存在?$_GET?或$this->_queryParams?中。優先使用?$this->_queryParams?的。 * getQueryParam()?對應于?get()?用于獲取特定的GET參數的情況。 而?post()?,?getPostParams()?和?getPostParam()?用于獲取POST參數: * post()?與?get()?類似,可以指定所要獲取的特定參數的參數名,在這個參數名不存在時,可以指定默認值。 當不指定參數名時,獲取所有的POST參數。 具體功能是由下面2個函數來實現的。 * getPostParam()?用于通過參數名獲取特定的POST參數,需要調用?getPostParams()?獲取所有的POST參數。 * getPostParams()?用于獲取所有的POST參數。 上面稍微復雜點的,可能就是?getPostParams()?了,我們就稍稍剖析下Yii是怎么解析POST參數的。 先講講這個方法所涉及到的一些東東:內容類型、請求解析器、請求體。 ### 內容類型(Content-Type)[](http://www.digpage.com/web_request.html#content-type "Permalink to this headline") 在?getPostParams()?中,需要先獲取請求體的內容類型,然后采用相應的解析器對內容進行解析。 獲取內容類型,使用?getContentType() ~~~ public function getContentType() { if (isset($_SERVER["CONTENT_TYPE"])) { return $_SERVER["CONTENT_TYPE"]; } elseif (isset($_SERVER["HTTP_CONTENT_TYPE"])) { return $_SERVER["HTTP_CONTENT_TYPE"]; } return null; } ~~~ 根據?[CGI 1.1 規范](https://tools.ietf.org/html/rfc3875.html)?, 內容類型由?CONTENT_TYPE?環境變量來表示。 而根據?[HTTP 1.1 協議](https://tools.ietf.org/html/rfc2616)?, 內容類型則是放在?CONTENT_TYPE?頭部中,然后由PHP賦值給?$_SERVER['HTTP_CONTENT_TYPE']?。 這里一般沒有沖突,因此發現哪個用哪個,就怕客戶端沒有給出(這種情況返回?null?)。 ### 請求解析器[](http://www.digpage.com/web_request.html#id7 "Permalink to this headline") 在?getPostParams()?中,根據不同的Content Type 創建了相應的內容解析器對請求體進行解析。yii\web\Request?使用成員變量?public?$parsers?來保存一系列的解析器。 這個變量在配置時進行指定: ~~~ 'request' => [ ... ... 'parsers' => [ 'application/json' => 'yii\web\JsonParser', ], ] ~~~ $parsers?是一個數組,數組的鍵是Content Type,如?applicaion/json?之類。 而數組的值則是對應于特定Content Type 的解析器,如?yii\web\JsonParser?。 這也是Yii實現的唯一一個現成的Parser,其他Content-Type,需要開發者自己寫了。 而且,可以以?*?為鍵指定一個解析器。那么該解析器將在一個Content Type找不到任何匹配的解析器后被使用。 yii\web\JsonParser?其實很簡單: ~~~ namespace yii\web; use yii\base\InvalidParamException; use yii\helpers\Json; // 所有的解析器都要實現 RequestParserInterface // 這個接口也只是要求實現 parse() 方法 class JsonParser implements RequestParserInterface { public $asArray = true; public $throwException = true; // 具體實現 parse() public function parse($rawBody, $contentType) { try { return Json::decode($rawBody, $this->asArray); } catch (InvalidParamException $e) { if ($this->throwException) { throw new BadRequestHttpException( 'Invalid JSON data in request body: ' . $e->getMessage(), 0, $e); } return null; } } } ~~~ 這里使用?yii\helpers\Json::decode()?對請求體進行解析。這個?yii\helpers\Json?是個輔助類, 專門用于處理JSON格式數據。具體的內容我們這里就不做講解了,只需要了解這里可以將JSON格式數據解析出來就OK了, 學有余力的讀者朋友可以自己看看代碼。 ### 請求體[](http://www.digpage.com/web_request.html#id8 "Permalink to this headline") 在?yii\web\Reqeust::getBodyParams()?和?yii\web\RequestParserInterface::parse()?中, 我們可以看到,需要將請求體傳入?parse()?進行解析,且請求體由?yii\web\Request::getRawBody()?可得。 yii\web\Request::getRawBody(): ~~~ public function getRawBody() { if ($this->_rawBody === null) { $this->_rawBody = file_get_contents('php://input'); } return $this->_rawBody; } ~~~ 這個方法使用了?php://input?來獲取請求體,這個?php://input?有這么幾個特點: * php://input?是個只讀流,用于獲取請求體。 * php://input?是返回整個HTTP請求中,除去HTTP頭部的全部原始內容, 而不管是什么Content Type(或稱為編碼方式)。 相比較之下,?$_POST?只支持?application/x-www-form-urlencoded?和multipart/form-data-encoded?兩種Content Type。其中前一種就是簡單的HTML表單以method="post"?提交時的形式, 后一種主要是用于上傳文檔。因此,對于諸如?application/json等Content Type,這往往是在AJAX場景下使用, 那么使用?$_POST?得到的是空的內容,這時就必須使用?php://input?。 * 相比較于?$HTTP_RAW_POST_DATA?,?php://input?無需額外地在php.ini中 激活always-populate-raw-post-data?,而且對于內存的壓力也比較小。 * 當編碼方式為?multipart/form-data-encoded?時,?php://input?是無效的。這種情況一般為上傳文檔。 這種情況可以使用傳統的?$_FILES?或者?yii\web\UploadedFile?。 ## 請求的頭部[](http://www.digpage.com/web_request.html#id9 "Permalink to this headline") yii\web\Request?使用一個成員變量?private?$_headers?來存儲請求頭。 而這個?$_header?其實是一個yii\web\HeaderCollection?,這是一個集合類的基本數據結構, 實現了SPL的?IteratorAggregate?,ArrayAccess?和?Countable?等接口。 因此,這個集合可以進行迭代、像數組一樣進行訪問、可被用于conut()?函數等。 這個數據結構相對簡單,我們就不展開占用篇幅了。我們要講的是怎么獲取請求的頭部。 這個是由yii\web\Request::getHeaders()?來實現的: ~~~ public function getHeaders() { if ($this->_headers === null) { // 實例化為一個HeaderCollection $this->_headers = new HeaderCollection; // 使用 getallheaders() 獲取請求頭部,以數組形式返回 if (function_exists('getallheaders')) { $headers = getallheaders(); // 使用 http_get_request_headers() 獲取請求頭部,以數組形式返回 } elseif (function_exists('http_get_request_headers')) { $headers = http_get_request_headers(); // 使用 $_SERVER 數組獲取頭部 } else { foreach ($_SERVER as $name => $value) { // 針對所有 $_SERVER['HTTP_*'] 元素 if (strncmp($name, 'HTTP_', 5) === 0) { // 將 HTTP_HEADER_NAME 轉換成 Header-Name 的形式 $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))); $this->_headers->add($name, $value); } } return $this->_headers; } // 將數組形式的請求頭部變成集合的元素 foreach ($headers as $name => $value) { $this->_headers->add($name, $value); } } return $this->_headers; } ~~~ 這里用3種方法來嘗試獲取請求的頭部: * getallheaders()?,這個方法僅在將PHP作為Apache的一個模塊運行時有效。 * http_get_request_headers()?,要求PHP啟用HTTP擴展。 * $_SERVER?數組的方法,需要遍歷整個數組,并將所有以?HTTP_*?元素加入到集合中去。 并且,要將所有?HTTP_HEADER_NAME?轉換成?Header-Name?的形式。 就是根據不同的PHP環境,采用有效的方法來獲取請求頭部,如此而已。 ## 請求的解析[](http://www.digpage.com/web_request.html#id10 "Permalink to this headline") 我們前面就說過了,無論是命令行應用還是Web應用,他們的請求都要實現接口要求的?resolve()?, 以便明確這個用戶請求的路由和參數。下面就是?yii\web\Request::resolve()?的代碼: ~~~ public function resolve() { // 使用urlManager來解析請求 $result = Yii::$app->getUrlManager()->parseRequest($this); if ($result !== false) { list ($route, $params) = $result; // 將解析出來的參數與 $_GET 參數進行合并 $_GET = array_merge($_GET, $params); return [$route, $_GET]; } else { throw new NotFoundHttpException(Yii::t('yii', 'Page not found.')); } } ~~~ 看著很簡單吧?這才幾行,還沒有?getBodyParams()?的代碼多呢。 雖然簡單,但是有一個細節我們要留意,就是在解析出路由信息和參數的時候, 會把參數的內容加入到?$_GET?中去,這是合理的。 比如,對于?http://www.digpage.com/post/view/100?這個 100 在路由規則中,其實定義為一個 參數。其原始的形式應當是?http://www.digpage.com/index.php?r=post/view&id=100?。你說該 不該把?id?=?100重新寫回?$_GET?去?至于路由規則的內容,可以看看?[_路由(Route)_](http://www.digpage.com/route.html#route)?的內 容。 從這個?resolve()?是看不出來解析過程的復雜的,這個?yii\web\Request::resolve()?是個沒擔當的家伙,他把解析過程推給了 urlManager。 那我們就順藤摸瓜,一睹這個yii\web\UrlManager::parseRequest()?吧: ~~~ public function parseRequest($request) { // 啟用了 enablePrettyUrl 的情況 if ($this->enablePrettyUrl) { // 獲取路徑信息 $pathInfo = $request->getPathInfo(); // 依次使用所有路由規則來解析當前請求 // 一旦有一個規則適用,后面的規則就沒有被調用的機會了 foreach ($this->rules as $rule) { if (($result = $rule->parseRequest($this, $request)) !== false) { return $result; } } // 所有路由規則都不適用,又啟用了 enableStrictParsing , // 那只能返回 false 了。 if ($this->enableStrictParsing) { return false; } // 所有路由規則都不適用,幸好還沒啟用 enableStrictParing, // 那就用默認的解析邏輯 Yii::trace( 'No matching URL rules. Using default URL parsing logic.', __METHOD__); // 配置時所定義的fake suffix,諸如 ".html" 等 $suffix = (string) $this->suffix; if ($suffix !== '' && $pathInfo !== '') { // 這個分支的作用在于確保 $pathInfo 不能僅僅是包含一個 ".html"。 $n = strlen($this->suffix); // 留意這個 -$n 的用法 if (substr_compare($pathInfo, $this->suffix, -$n, $n) === 0) { $pathInfo = substr($pathInfo, 0, -$n); // 僅包含 ".html" 的$pathInfo要之何用?掐死算了。 if ($pathInfo === '') { return false; } // 后綴沒匹配上 } else { return false; } } return [$pathInfo, []]; // 沒有啟用 enablePrettyUrl的情況,那就更簡單了, // 直接使用默認的解析邏輯就OK了 } else { Yii::trace( 'Pretty URL not enabled. Using default URL parsing logic.', __METHOD__); $route = $request->getQueryParam($this->routeParam, ''); if (is_array($route)) { $route = ''; } return [(string) $route, []]; } } ~~~ 從上面代碼中可以看到,urlManager是按這么一個順序來解析用戶請求的: * 先判斷是否啟用了 enablePrettyUrl,如果沒啟用,所有的路由和參數信息都在URL的查詢參數中, 很簡單就可以處理了。 * 通常都會啟用 enablePrettyUrl,由于路由和參數信息部分或全部變成了URL路徑。 經過了美化,使得URL看起來更友好,但化妝品總是比清水芙蓉要燒銀子,解析起來就有點費功夫了。 * 既然路由和參數信息變成了URL路徑,那么就先從URL路徑下手獲取路徑信息。 * 然后依次使用已經定義好的路由規則對當前請求進行解析,一旦有一個規則適用, 后續的路由規則就不會起作用了。 * 然后再對配置的?.html?等fake suffix進行處理。 這一過程中,有兩個重點,一個是獲取路徑信息,另一個就是使用路由規則對請求進行解析。下面我們依次進行講解。 ### 獲取路徑信息[](http://www.digpage.com/web_request.html#id11 "Permalink to this headline") 在大多數情況下,我們還是會啟用 enablePrettyUrl 的,特別是在產品環境下。那么從上面的代碼來看,?yii\web\Request::getPathInfo()?的調用就不可避免。其實涉及到獲取路徑信息的方法有很多, 都在?yii\web\Request?中,這里暴露出來的,只是一個?getPathInfo()?,相關的方法有: ~~~ // 這個方法其實是調用 resolvePathInfo() 來獲取路徑信息的 public function getPathInfo() { if ($this->_pathInfo === null) { $this->_pathInfo = $this->resolvePathInfo(); } return $this->_pathInfo; } // 這個才是重點 protected function resolvePathInfo() { // 這個 getUrl() 調用的是 resolveRequestUri() 來獲取當前的URL $pathInfo = $this->getUrl(); // 去除URL中的查詢參數部分,即 ? 及之后的內容 if (($pos = strpos($pathInfo, '?')) !== false) { $pathInfo = substr($pathInfo, 0, $pos); } // 使用PHP urldecode() 進行解碼,所有 %## 轉成對應的字符, + 轉成空格 $pathInfo = urldecode($pathInfo); // 這個正則列舉了各種編碼方式,通過排除這些編碼,來確認是 UTF-8 編碼 // 出處可參考 http://w3.org/International/questions/qa-forms-utf-8.html if (!preg_match('%^(?: [\x09\x0A\x0D\x20-\x7E] # ASCII | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 )*$%xs', $pathInfo) ) { $pathInfo = utf8_encode($pathInfo); } // 獲取當前腳本的URL $scriptUrl = $this->getScriptUrl(); // 獲取Base URL $baseUrl = $this->getBaseUrl(); if (strpos($pathInfo, $scriptUrl) === 0) { $pathInfo = substr($pathInfo, strlen($scriptUrl)); } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) { $pathInfo = substr($pathInfo, strlen($baseUrl)); } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) { $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl)); } else { throw new InvalidConfigException( 'Unable to determine the path info of the current request.'); } // 去除 $pathInfo 前的 '/' if ($pathInfo[0] === '/') { $pathInfo = substr($pathInfo, 1); } return (string) $pathInfo; } ~~~ 從?resolvePathInfo()?來看,需要調用到的方法有?getUrl()?resolveRequestUri()?getScriptUrl()getBaseUrl()?等,這些都是與路徑信息密切相關的,讓我們分別都看一看。 #### Request URI[](http://www.digpage.com/web_request.html#request-uri "Permalink to this headline") yii\web\Request::getUrl()?用于獲取Request URI的,實際上這只是一個屬性的封裝, 實質的代碼是在?yii\web\Request::resolveRequestUri()?中: ~~~ // 這個其實調用的是 resolveRequestUri() 來獲取當前URL public function getUrl() { if ($this->_url === null) { $this->_url = $this->resolveRequestUri(); } return $this->_url; } // 這個方法用于獲取當前URL的URI部分,即主機或主機名之后的內容,包括查詢參數。 // 這個方法參考了 Zend Framework 1 的部分代碼,通過各種環境下的HTTP頭來獲取URI。 // 返回值為 $_SERVER['REQUEST_URI'] 或 $_SERVER['HTTP_X_REWRITE_URL'], // 或 $_SERVER['ORIG_PATH_INFO'] + $_SERVER['QUERY_STRING']。 // 即,對于 http://www.digpage.com/index.html?helloworld, // 得到URI為 index.html?helloworld protected function resolveRequestUri() { // 使用了開啟了ISAPI_Rewrite的IIS if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { $requestUri = $_SERVER['HTTP_X_REWRITE_URL']; // 一般情況,需要去掉URL中的協議、主機、端口等內容 } elseif (isset($_SERVER['REQUEST_URI'])) { $requestUri = $_SERVER['REQUEST_URI']; // 如果URI不為空或以'/'打頭,則去除 http:// 或 https:// 直到第一個 / if ($requestUri !== '' && $requestUri[0] !== '/') { $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri); } // IIS 5.0, PHP以CGI方式運行,需要把查詢參數接上 } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { $requestUri = $_SERVER['ORIG_PATH_INFO']; if (!empty($_SERVER['QUERY_STRING'])) { $requestUri .= '?' . $_SERVER['QUERY_STRING']; } } else { throw new InvalidConfigException('Unable to determine the request URI.'); } return $requestUri; } ~~~ 從上面的代碼我們可以知道,Yii針對不同的環境下,PHP的不同表現形式,通過一些分支判斷, 給出一個統一的路徑名或文件名。 辛辛苦苦那么多,其實就是為了消除不同環境對于開發的影響,使開發者可以更加專注于核心工作。 其實,作為一個開發框架,無論是哪種語言、用于哪個領域, 都需要為開發者提供在各種環境下的都表現一致的編程界面。 這也是開發者可以放心使用的基礎條件,如果在使用框架之后, 開發者仍需要考慮各種環境下會怎么樣怎么樣,那么這個框架注定短命。 這里有必要點一點涉及到的幾個?$_SERVER?變量。這里面提到的,讀者朋友可以自行閱讀?[PHP文檔關于$_SERVER的內容](http://php.net/manual/en/reserved.variables.server.php)?, 也可以看看?[CGI 1.1 規范的內容](https://tools.ietf.org/html/rfc3875.html)?。 REQUEST_URI 由HTTP 1.1 協議定義,指訪問某個頁面的URI,去除開頭的協議、主機、端口等信息。 如http://www.digpage.com:8080/index.php/foo/bar?queryParams?, REQUEST_URI為/index.php/foo/bar?queryParams?。 X-REWRITE-URL 當使用以開啟了ISAPI_Rewrite 的IIS作為服務器時,ISAPI_Rewrite會在未對原始URI作任何修改前, 將原始的 REQUEST_URI 以 X-REWRITE-URL HTTP頭保存起來。 PATH_INFO CGI 1.1 規范所定義的環境變量。 從形式上看,http://www.digpage.com:8080/index.php?queryParams?。 它是整個URI中,在腳本標識之后、查詢參數???之前的部分。 對于Apache,需要設置?AcceptPathInfo?On?,且在一個URL沒有?部分的時候, PATH_INFO 無效。特殊的情況,如?http://www.digpage.com/index.php/, PATH_INFO 為?/?。 而對于Nginx,則需要設置: ~~~ fastcgi_split_path_info ^(.+?\.php)(/.*)$; fastcgi_param PATH_INFO $fastcgi_path_info; ~~~ ORIG_PATH_INFO 在PHP文檔中對它的解釋語焉不詳,“指未經PHP處理過的原始的PATH_INFO”。 這個在Apache和Nginx需要配置一番才行,但一般用不到,已經有PATH_INFO可以用了嘛。而在IIS中則有點怪, 對于http://www.digpage.com/index.php/?ORIG_PATH_INFO 為?/index.php/?; 對于http://www.digapge.com/index.php?ORIG_PATH_INFO 為?/index.php?。 根據上面這些背景知識,再來看?resolveRequestUri()?就簡單了: * 最廣泛的情況,應當是使用 REQUEST_URI 來獲取。但是?resolveRequestUri()?卻先使用 X-REWRITE-URL, 這是為了防止REQUEST_URI被rewrite。 * 其次才是使用 REQUEST_URI,這對于絕大多數情況是完全夠用的了。 * 但REQUEST_URI畢竟只是規范的要求,Web服務器很有可能店大欺客、另立山頭,我們又不是第一次碰見了是吧? 所以,Yii使用了平時比較少用到的ORIG_PATH_INFO。 * 最后,按照規范要求進行規范化,該去頭的去頭,該續尾的續尾。去除主機信息段和查詢參數段后, 就大功告成了。 #### 入口腳本路徑[](http://www.digpage.com/web_request.html#id14 "Permalink to this headline") yii\web\Request::getScriptUrl()?用于獲取入口腳本的相對路徑,也涉及到不同環境下PHP的不同表現。 我們還是先從代碼入手: ~~~ // 這個方法用于獲取當前入口腳本的相對路徑 public function getScriptUrl() { if ($this->_scriptUrl === null) { // $this->getScriptFile() 用的是 $_SERVER['SCRIPT_FILENAME'] $scriptFile = $this->getScriptFile(); $scriptName = basename($scriptFile); // 下面的這些判斷分支代碼,為各主流PHP framework所用, // Yii, Zend, Symfony等都是大同小異。 if (basename($_SERVER['SCRIPT_NAME']) === $scriptName) { $this->_scriptUrl = $_SERVER['SCRIPT_NAME']; } elseif (basename($_SERVER['PHP_SELF']) === $scriptName) { $this->_scriptUrl = $_SERVER['PHP_SELF']; } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) { $this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME']; } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) { $this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName; } elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) { $this->_scriptUrl = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $scriptFile)); } else { throw new InvalidConfigException( 'Unable to determine the entry script URL.'); } } return $this->_scriptUrl; } ~~~ 上面的代碼涉及到了一些環境問題,點一點,大家了解下就OK了: SCRIPT_FILENAME 當前腳本的實際物理路徑,比如?/var/www/digpage.com/frontend/web/index.php?, 或WIN平臺的D:\www\digpage.com\frontend\web\index.php?。 以Nginx為例,一般情況下,SCRIPT_FILENAME有以下配置項: ~~~ fastcgi_split_path_info ^(.+?\.php)(/.*)$; # 使用 document root 來得到物理路徑 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name ~~~ SCRIPT_NAME CGI 1.1 規范所定義的環境變量,用于標識CGI腳本(而非腳本的輸出), 如http://www.digapge.com/path/index.php?中的?/path/index.php?。 仍以Nginx為例,SCRIPT_NAME一般情況下有?fastcgi_param?SCRIPT_NAME?$fastcgi_script_name?的設置。 絕大多數情況下,使用 SCRIPT_NAME 即可獲取當前腳本。 PHP_SELF PHP_SELF 是PHP自己實現的一個?$_SERVER?變量,是相對于文檔根目錄(document root)而言的。 對于?http://www.digpage.com/path/index.php?queryParams?,PHP_SELF 為?/path/index.php?。 一般SCRIPT_NAME 與 PHP_SELF 無異。但是,在 PHP.INI 中,如?cgi.fix_pathinfo=1?(默認即為1)時, 對于形如?http://www.digpage.com/path/index.php/post/view/123?, 則PHP_SELF 為/path/index.php/post/view/123?。 而根據 CGI 1.1 規范,SCRIPT_NAME 僅為?/path/index.php?,至于剩余的?/post/view/123?則為PATH_INFO。 ORIG_SCRIPT_NAME 當PHP以CGI模式運行時,默認會對一些環境變量進行調整。 首當其沖的,就是 SCRIPT_NAME 的內容會變成?php.cgi?等二進制文件,而不再是CGI腳本文件。 當然,設置?cgi.fix_pathinfo=0?可以關閉這一默認行為。但這導致的副作用比較大,影響范圍過大,不宜使用。 但天無絕人之路,九死之地總留一線生機,那就是ORIG_SCRIPT_NAME,他保留了調整前 SCRIPT_NAME 的內容。 也就是說,在CGI模式下,可以使用 ORIG_SCRIPT_NAME 來獲取想要的SCRIPT_NAME。 請留意使用 ORIG_SCRIPT_NAME 前一定要先確認它是否存在。 交待完這些背景知識后,我們再來看看?yii\web\Request::getScriptUrl()?的邏輯: * 先調用?yii\web\Request::getScriptFile()?, 通過?basename($_SERVER['SCRIPT_FILENAME'])?獲取腳本文件名。一般都是我們的入口腳本?index.php?。 * 絕大多數情況下,?base($_SERVER('SCRIPT_NAME'))?是與第一步獲取的?index.php?相同的。 如果這樣的話,則認為這個 SCRIPT_NAME 就是我們所要的腳本URL。 這也是規范的定義。但是既然稱為規范,說明并非是事實。 事實是由Web服務器來實現的,也就是說Web服務器可能進行修改。 另外,對于運行于CGI模式的PHP而言,使用 SCRIPT_NAME 也無法獲得腳本名。 * 那么我們轉而向PHP_SELF求助,這個是PHP內部實現的,不受Web服務器的影響。一般這個PHP_SELF也是可堪一用的, 但也不是放之四海而皆準,對于帶有PATH_INFO的URI,basename()?獲取的并不是腳本名。 * 于是我們再轉向 ORIG_SCRIPT_NAME 求助,如果PHP是運行于CGI模式,那么就可行。 * 再不成功,可能PHP并非運行于CGI模式(否則第4步就可以成功),且URI中帶有PATH_INFO(否則第二步就可以成功)。 對于這種情形,PHP_SELF的前面一截就是我們要的腳本URL 。 * 萬一以上情況都不符合,說明當前PHP運行的環境詭異莫測。 那只能寄希望于將 SCRIPT_FILENAME 中前面那截可能是Document Root的部分去掉,余下的作為腳本URL了。 前提是要有Document Root,且SCRIPT_FILENAME前面的部分可以匹配上。 #### Base Url[](http://www.digpage.com/web_request.html#base-url "Permalink to this headline") 獲取路徑信息的最后一個相關方法,就是?yii\web\Request::getBaseUrl(): ~~~ // 獲取Base Url public function getBaseUrl() { if ($this->_baseUrl === null) { // 用上面的腳本路徑的父目錄,再去除末尾的 \ 和 / $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/'); } return $this->_baseUrl; } ~~~ 這個Base Url很簡單,相信聰明如你肯定一目了然,我就不浪費篇幅了。 好了,上面就是?yii\web\Request::resolve()?中有關獲取路徑信息的內容。 下一步就是使用路由規則去解析當前請求了。 ### 使用路由規則解析[](http://www.digpage.com/web_request.html#id15 "Permalink to this headline") 上面這么多有關從請求獲取路徑信息的內容,其實只完成了請求解析的第一步而已。 接下來,urlManager就要遍歷所有的路由規則來解析當前請求,直到有一個規則適用為止。 路由規則層面對于請求的解析,我們在?[_路由(Route)_](http://www.digpage.com/route.html#route)?的?[_解析URL_](http://www.digpage.com/route.html#parse-url)?部分已經講得很清楚了。 如果覺得《深入理解Yii2.0》對您有所幫助,也請[幫助《深入理解Yii2.0》](http://www.digpage.com/donate.html#donate)。 謝謝!
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看