--- tags: laravel --- # 請求Request ![](https://i.imgur.com/m5hyJqa.jpg) ![](https://i.imgur.com/N5dVa6z.jpg) ## 簡介 Laravel 的 Illuminate\Http\Request 類別提供一種物件導向的方式來讓你和應用的 HTTP 請求去進行互動。也就是取得由請求所提交的輸入項 . cookies 和 檔案 ## 與請求互動 ### 取得請求 要通過依賴注入取得當前的 HTTP 請求實例,你要在控制器上加入 Illuminate\Http\Request 類別。需要傳入的請求實例將會由服務容器來自動注入: ``` <?php namespace App\Http\Controllers; use Illuminate\Http\Request; class UserController extends Controller { /** * Store a new user. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { $name = $request->input('name'); // } } ``` 同樣的道理,你也可以在路由檔的 Closure 上面使用針對 Illuminate\Http\Request 類別作型別依賴的技巧。服務容器也會自動的在執行時把進來的請求自動注入進來 ``` use Illuminate\Http\Request; Route::get('/', function (Request $request) { // }); ``` ### 依賴注入 & 路徑參數 如果控制器需要從路徑參數中取值,你必須在其他依賴之後才列路徑參數。舉個例子,假如路由是這樣定義的: ``` use App\Http\Controllers\UserController; Route::put('/user/{id}', [UserController::class, 'update']); ``` 你可以這樣來定義控制器,在依賴注射 Illuminate\Http\Request 類別之後接著獲取路由參數 id: ``` <?php namespace App\Http\Controllers; use Illuminate\Http\Request; class UserController extends Controller { /** * Update the specified user. * * @param \Illuminate\Http\Request $request * @param string $id * @return \Illuminate\Http\Response */ public function update(Request $request, $id) { // } } ``` ### 請求路徑 & 方法 Illuminate\Http\Request 提供了各種方法來檢查應用的 HTTP 請求,並擴展了 Symfony\Component\HttpFoundation\Request 類別。 我們將在下面討論一些最重要的方法 ### 取得請求路徑 path() 返回請求的路徑訊息。比如接收到的請求路徑是 http://domain.com/foo/bar,則 path () 將回傳 foo/bar: `$uri = $request->path();` ### 確認請求路徑 / 路由 is() 能允許你去確認進來的請求路徑是否符合某種模式,在這方法內你能透過 wildcard(*)來代表任意值 ``` if ($request->is('admin/*')) { // } ``` 使用 routeIs() ,你能夠確認進來的請求是否符合某種名稱的路由: ``` if ($request->routeIs('admin.*')) { // } ``` ### 取得請求網址 要取得完整請求 URL,你可以使用 url() 或 fullUrl() 。 url() 回傳不帶有查詢條件(GET請求參數)的 URL,而 fullUrl() 的回傳則是包含查詢條件字串 ``` $url = $request->url(); $urlWithQueryString = $request->fullUrl(); ``` ### 取得請求方法 method() 將會回傳請求的 HTTP 動詞。 你也可以用 isMethod() 去驗證 HTTP 動詞是否為指定的方法: ``` $method = $request->method(); if ($request->isMethod('post')) { // } ``` ### 請求 Headers 你能從 Illuminate\Http\Request 實例的 header() 來取得請求標頭。假如 header 不存在請求之內,將會回傳 null。然而這個方法接受第二參數作為預設值當 header 不存在於請求時可以回傳 `$value = $request->header('X-Header-Name');` `$value = $request->header('X-Header-Name', 'default');` hasHeader() 用來確認請求是否包含指定標頭: ``` if ($request->hasHeader('X-Header-Name')) { // } ``` bearerToken() 能用來從驗證標頭取得 bearerToken。假如找不到的話,將會回傳空字串 `$token = $request->bearerToken();` ### 請求 IP 地址 ip() 能用來取得發動請求的客戶端 IP 地址 `$ipAddress = $request->ip();` ### Content Negotiation Laravel 提供多種方式來透過 Accept hader 確認請求所要求的內容類型。首先, getAcceptableContentTypes() 將回傳一個陣列裏頭包含所有請求能接受的內容類型 `$contentTypes = $request->getAcceptableContentTypes();` accepts() 接受一個陣列,內容為內容類型。如果陣列裏頭的內容類型有任何一個能夠被請求支持將會回傳 true ,否則回傳 false ``` if ($request->accepts(['text/html', 'application/json'])) { // ... } ``` 你也能夠使用 prefers() 來了解哪一種陣列裡的內容類型是請求最喜歡的。假如沒有任何一種內容類型能被請求接受,將會回傳 null `$preferred = $request->prefers(['text/html', 'application/json']);` 因為很多應用只接受 HTML 或者是 JSON,你能使用 expectsJson() 來確認該請求是否期待一個 JSON 回應 ``` if ($request->expectsJson()) { // ... } ``` ### 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 nyholm/psr7 ``` 安裝這些套件之後就可以通過路由 Closure 和控制器方法的請求介面的型別提示來獲取 PSR-7 請求: ``` //routes\web.php use Psr\Http\Message\ServerRequestInterface; Route::get('/', function (ServerRequestInterface $request) { // }); ``` >技巧: > >如果從路由或控制器返回 PSR-7 回應實例,框架將會自動將其轉換為 Laravel 的回應實例並用來顯示 [PSR-7的相關介紹](https://ithelp.ithome.com.tw/articles/10236390) ## 輸入項 ### 取得輸入項 #### 取得所有輸入項的值 你可以透過 all() 來取得請求的所有傳入資料,格式是陣列。不管是來自於 HTML 表單的請求又或者是 XHR 請求都可以使用 all() $input = $request->all(); #### 取得一個輸入項的值 使用一些簡單的方法,你就可以從 Illuminate\Http\Request 取得所有的用戶輸入資料而不用去管用戶到底是用哪種 HTTP 動詞。所有的 HTTP 動詞都可以用 input() 來取得用戶的某個輸入項資料: `$name = $request->input('name');` 你可以傳入預設值作為 input() 的第二參數,當請求的該輸入項為空時就會回傳預設值 `$name = $request->input('name', 'Sally');` 當表單有包含陣列輸入項的話,可以使用點語法來取用陣列的元素 ``` $name = $request->input('products.0.name'); $names = $request->input('products.*.name'); ``` 你也可以直接呼叫 input() 而不傳入任何參數,這時將會回傳所有的輸入項的值,以陣列的形式 `$input = $request->input();` #### 從查詢字串來取得值 當使用 input() 來取得請求的值時當然也有包含查詢字串,但假如你只想要取得查詢字串的值的話,可以改用 query() `$name = $request->query('name');` 你也可以將預設值作為 query() 的第二參數傳入,那樣的話,如果找不到值就會回傳預設值 `$name = $request->query('name', 'Helen');` 你也可以直接呼叫 query() 而不傳入任何參數,這時將會回傳所有的查詢字串輸入項的值,以陣列的形式 `$query = $request->query();` #### 取得 JSON 輸入項的值 當向應用傳遞 JSON 請求時,只要將 header 中的 Content-Type 設置為 application/json 後就可以使用 input() 來獲取 JSON 資料。你也可以使用點語法來取得 JSON 陣列內容: `$name = $request->input('user.name');` #### 取得 Boolean 輸入項的值 當處理諸如 checkbox 類的 HTML 標籤時,應用可能獲取到以字串形式傳遞的真或假的值,例如 「true」 或者 「on」。為了方便,你可以使用 bool() 來將這些值轉換成布林值類型。 boolean () 將會把 1、"1"、true、"true"、"on" 和 "yes" 這些內容回傳 true ,至於其他值將會回傳 false `$archived = $request->boolean('archived');` #### 透過動態方法來取得輸入項的值 你也可以通過 Illuminate\Http\Request 介面實例的動態屬性來取得用戶的輸入內容。例如你的表單中包含 name 輸入項,則可以通過下面這種方式取得 `$name = $request->name;` 當使用動態參數時, Laravel 會先去確認請求內是否有同名的輸入項。假如不存在的話, Laravel 將去搜尋是否有同名的路徑參數 #### 取得部分輸入項的值 如果你需要取得一部分輸入項的值,你可以使用 only() 或 except() 來限定需要的輸入項。它們接受陣列或者不定量參數列表 ``` $input = $request->only(['username', 'password']); $input = $request->only('username', 'password'); $input = $request->except(['credit_card']); $input = $request->except('credit_card'); ``` only() 方法將會回傳有符合陣列內元素名的輸入項的值,以鍵值對的形式,except() 則相反 ### 判斷某輸入項是否存在 你可以使用 has() 來判斷當前請求中是否含有指定輸入項的值。如果請求中存在該輸入項的值則 has () 將回傳 true: ``` if ($request->has('name')) { // } ``` 當你使用的是陣列時, has() 將會判斷是否所列的輸入項之值都存在 ``` if ($request->has(['name', 'email'])) { // } ``` whenHas() 將會在指定輸入項的值存在時執行 Closure ``` $request->whenHas('name', function ($input) { // }); ``` 同樣使用的是陣列時, hasAny() 會在判斷所列的輸入項之值其中有一個存在時回傳 true ``` if ($request->hasAny(['name', 'email'])) { // } ``` 假如你不僅要判斷是否有某個輸入項,且其值不能為空,那你可以改用 filled() ``` if ($request->filled('name')) { // } ``` whenFilled() 將會在指定輸入項的值存在且不為空時執行 Closure ``` $request->whenFilled('name', function ($input) { // }); ``` 如果你想要判斷一個值在請求中是否缺少,可以使用 missing() ``` if ($request->missing('name')) { // } ``` ### 已填資料(舊資料) Laravel 允許你在兩次請求之間保持輸入項資料。這個特性在驗證發現使用者填寫錯誤後重新填入表單資料時非常有用。不過如果你使用 Laravel 自帶的特性來進行驗證 , 不需要自己手動呼叫這些方法,因為一些 Laravel 內建的驗證功能將會自動呼叫它們 #### 將輸入資料閃存到 Session Illuminate\Http\Request 類別的 flash() 方法可以把當前的輸入項的值存到 Session,因此在用戶向應用發起的下一次請求時它們仍然可取得: `$request->flash();` 你也可以使用 flashOnly() 和 flashExcept() 將部分的請求資料傳送給 Session。這些方法在將密碼之類的敏感資料排除在 Session 之外的情況下很有用: ``` $request->flashOnly(['username', 'email']); $request->flashExcept('password'); ``` #### 將輸入項存入 Session 後進行轉址 如果你需要經常保存輸入項的值到 Session 然後轉址到先前的頁面,可以通過在呼叫 redirect() 後接續呼叫 withInput() 來實現: ``` return redirect('form')->withInput(); return redirect()->route('user.create')->withInput(); return redirect('form')->withInput( $request->except('password') ); ``` #### 取得舊數據 若要取得上一次請求所保存的舊資料,可以在 Illuminate\Http\Request 的實例上使用 old() 。old() 會從 Session 取出之前被存入的輸入項資料: `$username = $request->old('username');` Laravel 也提供了全域幫助函式 old() 。如果你要在 Blade 模板 中顯示舊的輸入項,使用 old() 將會更加方便。如果指定輸入項名稱沒有舊的輸入值,則會返回 null: `<input type="text" name="username" value="{{ old('username') }}">` ### Cookies #### 從請求中獲得 Cookies 所有從 Laravel 框架建立的 Cookies 都是加密的,並且使用授權碼進行簽名,這意味着如果它們被客戶端私自改變就會失效。若要從請求中取得 Cookie,在 Illuminate\Http\Request 實例使用 cookie() 即可: `$value = $request->cookie('name');` ### 輸入值精簡與統一化 預設的 Laravel 有包含 App\Http\Middleware\TrimStrings 和 App\Http\Middleware\ConvertEmptyStringsToNull 這兩個中介層在你的全域中介層堆中,也就是說它們被列在 App\Http\Kernel 類別裏頭。這些中介層將會自動清除進入應用請求的輸入值的前後空白內容,同時也會把所有的空字串轉成null值。你就不需要擔心要自己在路由或控制器去處理這些狀況 假如你想要關閉這個特性,你應該要把它們從 App\Http\Kernel 類別的 $middleware 屬性中去移除這兩個中介層 ## 檔案 ### 取得上傳的檔案 你可以使用 file() 或使用動態屬性從 Illuminate\Http\Request 實例中訪問上傳的檔案。 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 文件來了解這些方法的詳細信息 ### 儲存上傳檔案 要儲存上傳的文件,要先配置好檔案系統。你可以使用 UploadedFile 的 store() 把上傳檔案移動到你的某個磁碟上,該磁碟可能是位在本地檔案系統中的某個位置,甚至像 Amazon S3 這樣的雲儲存空間 store() 接受相對於檔案系統設定的儲存檔案根目錄的路徑。這個路徑不能包含檔案名稱,因為系統會自動生成唯一的 ID 作為檔案名 store() 還接受可選的第二參數,用於儲存檔案的磁碟名稱。這個方法會返回相對於儲存檔案根目錄的文件路徑: ``` $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'); ``` 完整檔案儲存範例 ``` // 取得完整檔名 $filenameWithExt = $request->file('pic')->getClientOriginalName(); // 只取檔名 $filename = pathinfo($filenameWithExt, PATHINFO_FILENAME); // 只取副檔名 $extension = $request->file('pic')->getClientOriginalExtension(); // 生成新檔名 $fileNameToStore = $filename.'_'.time().'.'.$extension; // 儲存圖片 $path = $request->file('pic')->storeAs('public/storage/images',$fileNameToStore); ``` > 說明 > > 如需了解更多檔案系統相關的資訊,請參考[檔案儲存 File Storage](/DlLeQR8cR2e7SgGODnwiAw) ## 設定可信的代理 如果你的應用運行在失效的 TLS / SSL 證書的負載平衡器後,你可能會注意到你的應用有時不能生成 HTTPS 網址。通常這是因為你的應用正在從阜號 80 上的負載平衡器轉發流量,卻不知道應該要生成HTTPS 網址。 要解決這個問題需要在 Laravel 應用中包含 App\Http\Middleware\TrustProxies 中介層,這使得你可以快速自定義應用信任的負載平衡器或代理。你的可信代理應該作為這個中介層的 $proxies 屬性的陣列元素之一。除了配置受信任的代理之外,還可以配置應該被信任的代理 $headers: ``` <?php namespace App\Http\Middleware; use Fideloper\Proxy\TrustProxies as Middleware; use Illuminate\Http\Request; class TrustProxies extends Middleware { /** * The trusted proxies for this application. * * @var string|array */ protected $proxies = [ '192.168.1.1', '192.168.1.2', ]; /** * The headers that should be used to detect proxies. * * @var int */ protected $headers = Request::HEADER_X_FORWARDED_ALL; } ``` >技巧: > >如果你使用 AWS 彈性負載平衡,你的 $header 值應該是 Request::HEADER_X_FORWARDED_AWS_ELB。如果您想查看更多可用於 $headers 的屬性資料,請查閱 Symfony 關於信任代理的文件 ### 信任所有代理 如果你使用 Amazon AWS 或其他的「雲」的負載平衡服務,你可能不知道負載平衡器的實際 IP 地址。在這種情況下,你可以使用 * 來信任所有代理,也就是這樣設定: ``` /** * The trusted proxies for this application. * * @var string|array */ protected $proxies = '*'; ```