[TOC]
# 訪問請求實例
在控制器中獲取當前 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://domain.com/user/1`,則 `path` 方法將會返回 `user/1`:
~~~
$path = $request->path();
~~~
`is` 方法允許你驗證請求路徑是否與給定模式匹配。該方法參數支持 `*` 通配符:
~~~
if($request->is('user/*')){
//
}
~~~
如果請求URL是 `http://domain.com/user/1`,該方法會返回 `true`。
**獲取請求 URL**
想要獲取完整的 URL,而不僅僅是路徑信息,可以使用請求實例提供的 `url` 或 `fullUrl` 方法, `url` 方法返回不帶查詢字符串的 URL,而 `fullUrl` 方法返回結果則包含查詢字符串:
~~~
// 不包含查詢字符串
$url = $request->url();
// 包含查詢字符串
$url_with_query = $request->fullUrl();
~~~
例如,我們請求 `http://domain.com/user/1?token=laravelacademy.org`,則上述 `$url` 的值是 `http://domain.com/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文檔](http://api.symfony.com/4.0/Symfony/Component/HttpFoundation/File/UploadedFile.html)了解更多信息。
## 保存上傳的文件
要保存上傳的文件,需要使用你所配置的某個`文件系統`,對應配置位于 `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('未獲取到上傳文件或上傳過程出錯');
});
~~~
我們還是使用 Advanced REST Client 工具來演示 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 信任代理文檔](http://symfony.com/doc/current/deployment/proxies.html)。
**信任所有代理**
如果你在使用 Amazon AWS 或者其他云服務提供的負載均衡,并不知道均衡器真實的 IP 地址,這種情況下,可以使用 `*` 通配符信任所有代理:
~~~
/**
* The trusted proxies for this application.
*
* @var array
*/
protected $proxies = '*';
~~~
- 序言
- 新版特性
- 快速入門
- 升級指南
- 貢獻指南
- API文檔
- 安裝配置
- 目錄結構
- Homestead
- Valet
- 部署
- 核心概念
- 請求生命周期
- 服務容器
- 服務提供者
- 門面(Facades)
- 契約(Contracts)
- 框架基礎
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- 生成 URL
- Session
- 驗證
- 錯誤處理
- 日志
- 前端開發
- Blade 模板
- 本地化
- 前端腳手架
- 編譯前端資源(Laravel Mix)
- 安全系列
- 登錄認證
- API 認證
- 授權
- 加密
- 哈希
- 重置密碼
- 進階系列
- Artisan 控制臺
- 集合
- 廣播
- 緩存
- 事件
- 文件存儲
- 輔助函數
- 郵件
- 通知
- 擴展包開發
- 隊列
- 任務調度
- 數據庫操作
- 快速入門
- 查詢構建器
- 分頁
- 遷移
- 數據填充
- Redis
- Eloquent ORM
- 快速入門
- 關聯關系
- 集合
- 訪問器 & 修改器
- API 資源類
- 序列化
- 應用測試
- 快速入門
- HTTP 測試
- 瀏覽器測試
- 數據庫測試
- 模擬
- 官方擴展包
- Cashier(訂閱支付解決方案)
- Envoy(遠程操作解決方案)
- Horizon(隊列系統解決方案)
- Passport(API 認證解決方案)
- Scout(全文搜索解決方案)
- Socialite(第三方登錄解決方案)
- 相關下載
- Laravel 5.6 中文文檔離線版
- Laravel 5.6 一鍵安裝包