[ Laravel 5.8 文件 ] 基礎元件 —— HTTP 請求
訪問請求例項
在控制器中獲取當前 HTTP 請求例項,需要在建構函式或方法中對 Illuminate\Http\Request
類進行依賴注入,這樣當前請求例項會被服務容器自動注入:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class UserController extends Controller { /** * 儲存新使用者 * * @param Request $request * @return Response */ public function store(Request $request) { $name = $request->input('name'); // } }
依賴注入 & 路由引數
如果還期望在控制器方法中獲取路由引數,只需要將路由引數置於其它依賴之後即可,例如,如果你的路由定義如下:
Route::put('user/{id}','UserController@update');
你仍然可以對 Illuminate\Http\Request
進行依賴注入並通過如下方式定義控制器方法來訪問路由引數 id
:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class UserController extends Controller { /** * 更新指定使用者 * * @param Request $request * @param int $id * @return Response */ public function update(Request $request, $id) { // } }
通過路由閉包訪問請求
還可以在路由閉包中注入 Illuminate\Http\Request
,在執行閉包函式的時候服務容器會自動注入輸入請求:
use Illuminate\Http\Request; Route::get('/', function (Request $request) { // });
請求路徑 & 方法
Illuminate\Http\Request
繼承自 Symfony\Component\HttpFoundation\Request
類,提供了多個方法來檢測應用的 HTTP 請求,下面我們來演示其提供的一些獲取請求路徑和請求方式的方法:
獲取請求路徑
path
方法將會返回請求的路徑資訊,因此,如果請求URL是 http://blog.test/user/1
,則 path
方法將會返回 user/1
:
$path = $request->path();
is
方法允許你驗證請求路徑是否與給定模式匹配。該方法引數支援 *
萬用字元:
if($request->is('user/*')){ // }
如果請求URL是 http://blog.test/user/1
,該方法會返回 true
。
獲取請求 URL
想要獲取完整的 URL,而不僅僅是路徑資訊,可以使用請求例項提供的 url
或 fullUrl
方法, url
方法返回不帶查詢字串的 URL,而 fullUrl
方法返回結果則包含查詢字串:
// 不包含查詢字串 $url = $request->url(); // 包含查詢字串 $url_with_query = $request->fullUrl();
例如,我們請求 http://blog.test/user/1?token=laravelacademy.org
,則上述 $url
的值是 http://blog.test/user/1
, $url_with_query
的值是 http://blog.test/user/1?token=laravelacademy.org
。
獲取請求方法
method
方法將會返回 HTTP 請求方式。你還可以使用 isMethod
方法來驗證 HTTP 請求方式是否匹配給定字串:
$method = $request->method(); // GET/POST if($request->isMethod('post')){ // true or false }
PSR-7 請求
PSR-7 標準 指定了 HTTP 訊息介面,包括請求和響應。如果你想要獲取遵循 PSR-7 標準的請求例項而不是 Laravel 請求例項,首先需要安裝一些庫。Laravel 可以使用 Symfony HTTP Message Bridge 元件將典型的 Laravel 請求和響應轉化為相容 PSR-7 介面的實現:
composer require symfony/psr-http-message-bridge composer require zendframework/zend-diactoros
安裝完這些庫之後,只需要在路由或控制器中通過對請求示例進行型別提示就可以獲取 PSR-7 請求:
use Psr\Http\Message\ServerRequestInterface; Route::get('/', function (ServerRequestInterface $request) { // });
對比下 Request
例項和 ServerRequestInterface
例項的資料結構,可以看到 Laravel 的 Request 例項提供資訊更豐富:
注:如果從路由或控制器返回的是 PSR-7 響應例項,則其將會自動轉化為 Laravel 響應例項並顯示出來。
請求字串處理
預設情況下,Laravel 在 App\Http\Kernel
的全域性中介軟體堆疊中引入了 TrimStrings
和 ConvertEmptyStringsToNull
中介軟體。這些中介軟體會自動對請求中的字串欄位進行處理,前者將字串兩端的空格清除,後者將空字串轉化為 null
。這樣,在路由和控制器中我們就不必對字串欄位做額外的處理:
如果你想要禁止該行為,可以從 App\Http\Kernel
的中介軟體堆疊屬性 $middleware
中移除這兩個中介軟體。
獲取請求輸入
獲取所有輸入值
你可以使用 all
方法以陣列格式獲取所有輸入值:
$input = $request->all();
如果請求 URL 是 http://blog.test/user/1?token=laravelacademy.org&name=學院君
,則對應 $input
返回值是:
獲取單個輸入值
通過一些很簡單的方法,就可以從 Illuminate\Http\Request
例項中訪問使用者輸入。你不需要關心請求所使用的 HTTP 請求方式,因為對所有請求方式都是通過 input
方法獲取使用者輸入:
$name = $request->input('name');
還是以上面的請求 URL 為例,這裡的 $name
值是 學院君
。
你還可以傳遞一個預設值作為第二個引數給 input
方法,如果請求輸入值在當前請求 URL 中未出現時該值將會被返回:
$name = $request->input('name', '學院君');
比如我們訪問 http://blog.test/user/1?token=laravelacademy.org
,仍然可以獲取到 $name
的值為 學院君
。
處理表單陣列輸入時,可以使用”.”來訪問陣列輸入:
$input = $request->input('products.0.name'); $names = $request->input('products.*.name');
比如我們訪問 http://blog.test/user/1?products[][name]=學院君&products[][name]=學院君小號
,則上述 $input
的值是 學院君
,而 $names
的值是:
從查詢字串中獲取輸入
input
方法會從整個請求負載(包括查詢字串)中獲取數值, query
則只會從查詢字串中獲取數值:
$name = $request->query('name');
如果請求的查詢字串中沒有提供給定的查詢項,我們可以像 input
方法一樣設定第二個引數為預設值:
$name = $request->query('name', '學院君');
你也可以呼叫一個不傳任何引數的 query
方法以便以關聯陣列的方式獲取整個查詢字串的值,類似 all
方法所做的:
$query = $request->query();
寫到這裡,估計有些人要蒙圈了,那 input
和 query
到底有什麼區別,官方文件還是有些含混不清,那麼這裡學院君一杆子打到底,跟你聊聊兩者的本質區別,回到上面列印 Request 例項那張圖:
注意到標紅圈的部分, query
方法就是從 query
屬性物件中獲取引數值, input
方法會從 query + request
屬性物件中獲取引數值,請求例項上還有個 post
方法用於從 request
屬性物件中獲取引數值,講到這裡我們應該有點眉目了, query
方法用於獲取 GET
請求查詢字串引數值, input
方法用於獲取所有 HTTP 請求引數值, post
方法用於獲取 POST
請求引數值。感興趣的同學可以去底層檢視下原始碼: vendor/laravel/framework/src/Illuminate/Http/Concerns/InteractsWithInput.php
。
通過動態屬性獲取輸入
此外,你還可以通過使用 Illuminate\Http\Request
例項上的動態屬性來訪問使用者輸入。例如,如果你的應用表單包含 name
欄位,那麼可以像這樣訪問提交的值:
$name = $request->name;
使用動態屬性的時候,Laravel 首先會在請求中查詢引數的值,如果不存在,還會到路由引數中查詢。該功能的實現原理自然是魔術函式 __get
了:
獲取JSON輸入值
傳送 JSON 請求到應用的時候,只要 Content-Type
請求頭被設定為 application/json
,都可以通過 input
方法獲取 JSON 資料,還可以通過“.”號解析陣列:
$name = $request->input('user.name');
獲取輸入的部分資料
如果你需要取出輸入資料的子集,可以使用 only
或 except
方法,這兩個方法都接收一個數組或動態列表作為唯一引數,這和我們在上一篇控制器中提到的控制器中介軟體使用語法類似:
$input = $request->only(['username', 'password']); $input = $request->only('username', 'password'); $input = $request->except(['credit_card']); $input = $request->except('credit_card');
注: only
方法返回所有你想要獲取的引數鍵值對,不過,如果你想要獲取的引數不存在,則對應引數會被過濾掉。
判斷請求引數是否存在
判斷引數在請求中是否存在,可以使用 has
方法,如果引數存在則返回 true
:
if ($request->has('name')) { // }
該方法支援以陣列形式查詢多個引數,這種情況下,只有當引數都存在時,才會返回 true
:
if ($request->has(['name', 'email'])) { // }
如果你想要判斷引數存在且引數值不為空,可以使用 filled
方法:
if ($request->filled('name')) { // }
上一次請求輸入
Laravel 允許你在兩次請求之間儲存上一次輸入資料,這個特性在檢測校驗資料失敗後需要重新填充表單資料時很有用,不過如果你使用的是 Laravel 自帶的驗證功能,則不需要手動使用這些方法,因為一些 Laravel 自帶的校驗設定會自動呼叫它們。
將輸入儲存到 Session
Illuminate\Http\Request
例項的 flash
方法會將當前輸入存放到一次性 Session(所謂的一次性指的是從 Session 中取出資料後,對應資料會從 Session 中銷燬)中,這樣在下一次請求時上一次輸入的資料依然有效:
$request->flash();
你還可以使用 flashOnly
和 flashExcept
方法將輸入資料子集存放到 Session 中,這些方法在 Session 之外儲存敏感資訊時很有用,該功能適用於登入密碼填寫錯誤的場景:
$request->flashOnly('username', 'email'); $request->flashExcept('password');
將輸入儲存到 Session 然後重定向
如果你經常需要一次性儲存輸入請求輸入並返回到表單填寫頁,可以在 redirect
之後呼叫 withInput
方法實現這樣的功能:
return redirect('form')->withInput(); return redirect('form')->withInput($request->except('password'));
取出上次請求資料
要從 Session 中取出上次請求的輸入資料,可以使用 Request 例項提供的 old
方法。 old
方法可以很方便地從Session 中取出一次性資料:
$username = $request->old('username');
Laravel 還提供了一個全域性的輔助函式 old()
,如果你是在Blade 模板中顯示上次輸入資料,使用輔助函式 old()
更方便,如果給定引數沒有對應輸入,返回 null
:
<input type="text" name="username" value="{{ old('username') }}">
Cookie
從請求中取出Cookie
為了安全起見,Laravel 框架建立的所有 Cookie 都經過加密並使用一個認證碼進行簽名,這意味著如果客戶端修改了它們則需要對其進行有效性驗證。我們使用 Illuminate\Http\Request
例項的 cookie
方法從請求中獲取 Cookie 的值:
$value = $request->cookie('name');
此外,還可以使用 Cookie
門面獲取 Cookie 值:
$value = Cookie::get('name');
新增 Cookie 到響應
你可以使用 cookie
方法將一個 Cookie 新增到返回的 Illuminate\Http\Response
例項,你需要傳遞 Cookie 名稱、值、以及有效期(分鐘)到這個方法:
return response('歡迎來到 Laravel 學院')->cookie( 'name', '學院君', $minutes );
cookie
方法可以接收一些使用頻率較低的引數,一般來說,這些引數和 PHP 原生函式 setcookie 作用和意義一致:
return response('歡迎來到 Laravel 學院')->cookie( 'name', '學院君', $minutes, $path, $domain, $secure, $httpOnly );
我們簡單演示下該功能的使用,在 routes/web.php
定義兩個路由如下:
Route::get('cookie/add', function () { $minutes = 24 * 60; return response('歡迎來到 Laravel 學院')->cookie('name', '學院君', $minutes); }); Route::get('cookie/get', function(\Illuminate\Http\Request $request) { $cookie = $request->cookie('name'); dd($cookie); });
先訪問 http://blog.test/cookie/add
設定 Cookie,然後再通過 http://blog.test/cookie/get
獲取 Cookie 值,如果在頁面看到輸出 學院君
,則表示 Cookie 設定成功。當然我們也可以通過 Chrome 瀏覽器的 F12 模式快速檢視 Cookie 資訊:
被加密過的 name
就是我們剛剛設定的 Cookie 了。
此外,你還可以使用 Cookie
門面將應用於附件的 Cookie 推送到輸出響應佇列。 queue
方法接收一個 Cookie
例項或者建立一個 Cookie
例項的必要引數。這些 Cookie 會在響應傳送到瀏覽器之前新增上:
Cookie::queue(Cookie::make('name', 'value', $minutes)); Cookie::queue('name', 'value', $minutes);
生成 Cookie 例項
如果你想要生成一個 Symfony\Component\HttpFoundation\Cookie
例項以便後續新增到響應例項,可以使用全域性輔助函式 cookie
,該 Cookie 只有在新增到響應例項上才會傳送到客戶端:
$cookie = cookie('name', '學院君', $minutes); return response('歡迎來到 Laravel 學院')->cookie($cookie);
我們改寫下之前的 cookie/add
路由實現邏輯:
Route::get('cookie/add', function () { $minutes = 24 * 60; //return response('歡迎來到 Laravel 學院')->cookie('name', '學院君', $minutes); $cookie = cookie('name', '學院君X', $minutes); return response('歡迎來到 Laravel 學院')->cookie($cookie); });
效果和之前一致,這次訪問 cookie/get
路由,頁面列印結果是 學院君X
。
檔案上傳
獲取上傳的檔案
可以使用 Illuminate\Http\Request
例項提供的 file
方法或者動態屬性來訪問上傳檔案, file
方法返回 Illuminate\Http\UploadedFile
類的一個例項,該類繼承自 PHP 標準庫中提供與檔案互動方法的 SplFileInfo
類:
$file = $request->file('photo'); $file = $request->photo;
你可以使用 hasFile
方法判斷檔案在請求中是否存在:
if ($request->hasFile('photo')) { // }
驗證檔案是否上傳成功
使用 isValid
方法判斷檔案在上傳過程中是否出錯:
if ($request->file('photo')->isValid()){ // }
檔案路徑 & 副檔名
UploadedFile
類還提供了訪問上傳檔案絕對路徑和副檔名的方法。 extension
方法可以基於檔案內容判斷副檔名,該副檔名可能會和客戶端提供的副檔名不一致:
$path = $request->photo->path(); $extension = $request->photo->extension();
其他檔案方法
UploadedFile
例項上還有很多其他可用方法,檢視 該類的API文件 瞭解更多資訊。
儲存上傳的檔案
要儲存上傳的檔案,需要使用你所配置的某個檔案系統,對應配置位於 config/filesystems.php
:
Laravel 預設使用 local
配置存放上傳檔案,即本地檔案系統,預設根目錄是 storage/app
, public
也是本地檔案系統,只不過存放在這裡的檔案可以被公開訪問,其對應的根目錄是 storage/app/public
,要讓 Web 使用者訪問到該目錄下存放檔案的前提是在應用入口 public
目錄下建一個軟鏈 storage
連結到 storage/app/public
。
UploadedFile
類有一個 store
方法,該方法會將上傳檔案移動到相應的磁碟路徑上,該路徑可以是本地檔案系統的某個位置,也可以是雲端儲存(如Amazon S3)上的路徑。
store
方法接收一個檔案儲存的相對路徑(相對於檔案系統配置的根目錄 ),該路徑不需要包含檔名,因為系統會自動生成一個唯一ID作為檔名。
store
方法還接收一個可選的引數 —— 用於儲存檔案的磁碟名稱作為第二個引數(對應檔案系統配置 disks
的鍵名,預設值是 local
),該方法會返回相對於根目錄的檔案路徑:
$path = $request->photo->store('images'); $path = $request->photo->store('images', 's3');
如果你不想自動生成檔名,可以使用 storeAs
方法,該方法接收儲存路徑、檔名和磁碟名作為引數:
$path = $request->photo->storeAs('images', 'filename.jpg'); $path = $request->photo->storeAs('images', 'filename.jpg', 's3');
下面我們來簡單演示下檔案上傳功能,在 routes/api.php
中定義如下檔案上傳路由:
Route::post('file/upload', function(\Illuminate\Http\Request $request) { if ($request->hasFile('photo') && $request->file('photo')->isValid()) { $photo = $request->file('photo'); $extension = $photo->extension(); //$store_result = $photo->store('photo'); $store_result = $photo->storeAs('photo', 'test.jpg'); $output = [ 'extension' => $extension, 'store_result' => $store_result ]; print_r($output);exit(); } exit('未獲取到上傳檔案或上傳過程出錯'); });
我們可以使用 Chrome 瀏覽器擴充套件 Advanced REST Client 工具(或者 Postman)來演示 POST 表單提交:
標記紅圈的地方是需要重點關注的輸入和輸出。我分別測試了 store
方法和 storeAs
方法,上傳檔案成功後可以去 storage/app
目錄下檢視:
其他儲存介質使用方式也差不多,無非是修改下 store
和 storeAs
對應的引數。在使用過程中遇到什麼問題,歡迎在評論區反饋。
配置信任代理
如果你的應用執行在一個會中斷 TLS/SSL 證書的負載均衡器之後,你會注意到有的時候應用不會生成 HTTPS 連結,通常這是因為應用是從負載均衡器從80埠轉發過來的流量,所以不知道應該生成安全加密連結。
要解決這個問題可以使用 App\Http\Middleware\TrustProxies
中介軟體,該中介軟體允許你快速自定義需要被應用信任的負載均衡器或代理。被信任的代理位於這個中介軟體的 $proxies
屬性列表,除了配置信任代理之外,還可以配置代理髮送的帶有請求來源資訊的訊息頭:
<?php namespace App\Http\Middleware; use Illuminate\Http\Request; use Fideloper\Proxy\TrustProxies as Middleware; class TrustProxies extends Middleware { /** * The trusted proxies for this application. * * @var array */ protected $proxies = [ '192.168.1.1', '192.168.1.2', ]; /** * The headers that should be used to detect proxies. * * @var string */ protected $headers = Request::HEADER_X_FORWARDED_ALL; }
注:如果你在使用 AWS Elastic Load Balancing, headers
值應該修改為 Request::HEADER_X_FORWARDED_AWS_ELB
,關於 headers
屬性可用的更多常量,請檢視 Symfony 信任代理文件 。
信任所有代理
如果你在使用 Amazon AWS 或者其他雲服務提供的負載均衡,並不知道均衡器真實的 IP 地址,這種情況下,可以使用 *
萬用字元信任所有代理:
/** * The trusted proxies for this application. * * @var array */ protected $proxies = '*';