--- tags: laravel --- # 回應Response ![](https://i.imgur.com/ashYLZx.jpg) ![](https://i.imgur.com/SD6JuF5.jpg) ## 建立回應 ### 字串 & 陣列 所有路由和控制器處理完業務邏輯之後都會回傳一個發送到用戶瀏覽器的回應,Laravel 提供了多種不同的方式來進行回應,最基本的回應就是從路由或控制器回傳一個簡單的字串,框架會自動將這個字串轉化為一個完整的 HTTP 回應: ``` Route::get('/', function () { return 'Hello World'; }); ``` 除了從路由或控制器返回字串之外,還可以回傳陣列。框架會自動將陣列轉化為一個 JSON 回應: ``` Route::get('/', function () { return [1, 2, 3]; }); ``` > 技巧: > > 你知道還可以從路由或控制器返回 Eloquent 集合嗎?它們也會被自動轉化為 JSON 回應 ### 回應對象 通常,我們並不只是從路由方法單純回傳字串和陣列。相對的都會回傳一個完整的 Illuminate\Http\Response 實例或視圖 返回完整的 Response 實例允許你自定義回應的 HTTP 狀態碼和 headers。 Response 實例 繼承自 Symfony\Component\HttpFoundation\Response 類別, 該類別提供了各種構建 HTTP 回應的方法 ``` Route::get('/home', function () { return response('Hello World', 200) ->header('Content-Type', 'text/plain'); }); ``` ### 添加回應頭 大部分的回應方法都是可串接其他方法來呼叫的,使得創建回應實例的過程更具可讀性。例如你可以在回應返回給用戶前使用 header() 為其添加一系列的頭資訊: ``` return response($content) ->header('Content-Type', $type) ->header('X-Header-One', 'Header Value') ->header('X-Header-Two', 'Header Value'); ``` 否則,你能使用 withHeaders() 去指定一個 headers 陣列被加在回應上 ``` return response($content) ->withHeaders([ 'Content-Type' => $type, 'X-Header-One' => 'Header Value', 'X-Header-Two' => 'Header Value', ]); ``` #### 快取控制中介層 Laravel 包含了一個 cache.headers 中介層,可以用來快速地為路由群組設置 Cache-Control 標頭。如果在指令集中聲明了 etag, Laravel 會自動將 ETag 辨識符設置為回應內容的 MD5 Hash 值: ``` Route::middleware('cache.headers:public;max_age=2628000;etag')->group(function () { Route::get('/privacy', function () { // ... }); Route::get('/terms', function () { // ... }); }); ``` ### 附加 Cookies 到回應 你可以使用回應上的 cookie() 輕鬆地為回應增加 Cookies。例如你可以像這樣使用 cookie() 生成一個 Cookie 並輕鬆地將其附加到回應上。你應該透過這個方法傳送 name. value 和 cookie 應該被視為有效的分鐘數 ``` return response('Hello World')->cookie( 'name', 'value', $minutes ); ``` cookie() 還接受一些不太頻繁使用的參數。通常這些參數與原生PHP的 setcookie() 的參數有着相同的目的和含義: ``` return response('Hello World')->cookie( 'name', 'value', $minutes, $path, $domain, $secure, $httpOnly ); ``` 假如你想要確保有一個 Cookie 能透過發出的回應被送出,但是你還沒有回應的實例的話。你能夠使用 Cookie Facade 來隊列這些要附加的 Cookies 直到被送出。 queue() 接受所有建立 cookie 實例所需要的參數,這些 cookies 將會在送到瀏覽器之前附加在送出的回應上 ``` use Illuminate\Support\Facades\Cookie; Cookie::queue('name', 'value', $minutes); ``` #### 生成 Cookie 實例 假如你想要生成一個 Symfony\Component\HttpFoundation\Cookie 實例,並希望它能在稍晚被附加在回應實例上,你應該使用全域的 cookie() 。 這個 Cookie 將不會被傳回到瀏覽器除非它被附加在一個回應實例上 ``` $cookie = cookie('name', 'value', $minutes); return response('Hello World')->cookie($cookie); ``` #### 提早讓 Cookie 過期 你可以對回應呼叫 withoutCookie() 來透過使之過期的方式來移除一個 Cookie `return response('Hello World')->withoutCookie('name');` 假如你仍未得到回應實例,你可以透過 Cookie Facade 的 queue() 來讓 Cookie 提早過期 `Cookie::queue(Cookie::forget('name'));` ### Cookies & Encryption 預設情況下,Laravel 生成的所有 Cookie 都是經過加密和簽名,因此不能被客戶端修改或者讀取。 如果你想要應用生成的部分 Cookie 不被加密,那可以透過在 app/Http/Middleware 資料夾中 App\Http\Middleware\EncryptCookies 中介層的 $except 屬性 ``` /** * The names of the cookies that should not be encrypted. * * @var array */ protected $except = [ 'cookie_name', ]; ``` ## 轉址 轉址回應是 Illuminate\Http\RedirectResponse 類別的實例,並且包含用戶需要轉址至另一個 URL 所需的 header。Laravel 提供了幾種方法用於生成 RedirectResponse 實例。其中最簡單的方法是使用全域幫助函式 redirect() ``` Route::get('/dashboard', function () { return redirect('home/dashboard'); }); ``` 有時候你可能希望將用戶轉址到前一個網址,比如提交的表單有問題時。這時你可以使用全域幫助函式 back() 來執行此操作。由於這個功能是透過 Session,請確保呼叫 back() 的路由有分配 web 中介層群組 ``` Route::post('/user/profile', function () { // Validate the request... return back()->withInput(); }); ``` ### 轉址到命名路由 如果呼叫不帶參數的幫助函式 redirect() 時,會回傳 Illuminate\Routing\Redirector 實例。這個實例允許你呼叫 Redirector 實例上的任何方法。例如為命名路由生成 RedirectResponse,可以使用 route() `return redirect()->route('login');` 假如你的路由有參數需要提供,你可將之以陣列的格式作為第二參數傳入 route() ``` // 對應路由 URI: /profile/{id} return redirect()->route('profile', ['id' => 1]); ``` #### 通過 Eloquent Models 充填參數 如果你要轉址到使用 Eloquent 模型填充 id 參數的路由,可以直接傳遞模型本身,而 id 會被自動抓出 ``` // 對應的路由 URI: /profile/{id} return redirect()->route('profile', [$user]); ``` 如果你想要自定義這個路由參數中的預設參數名稱,需要覆寫模型實例上的 getRouteKey() 或者指定路由參數(/profile/{id:slug}) ``` /** * 取得模型的路由 key. * * @return mixed */ public function getRouteKey() { return $this->slug; } ``` ### 轉址到控制器方法 你還可以生成到控制器方法的轉址。要達到這個目的,只要把控制器和方法的名稱傳遞給 action() ``` use App\Http\Controllers\UserController; return redirect()->action([UserController::class, 'index']); ``` 假如你的控制器路由需要參數,你可以將之作為第二參數傳進 action() ``` return redirect()->action( [UserController::class, 'profile'], ['id' => 1] ); ``` ### 轉址到外部網域 有時候你需要轉址到應用外的網域。呼叫 away() 可以達到此目的,它會創建一個不帶有任何額外的 URL 編碼、有效性驗證檢查的 RedirectResponse 實例 `return redirect()->away('https://www.google.com');` ### 轉址並使用 Session 資料 轉址到新的 URL 的同時傳送資料給 Session 是很常見的。 通常會在成功執行一個動作並傳送訊息給 session,以便在新的頁面可以顯示這個訊息。為了方便起見,你可以創建一個 RedirectResponse 實例並接著在後面呼叫相關方法來將數據傳送給 Session : ``` Route::post('/user/profile', function () { // ... return redirect('dashboard')->with('status', 'Profile updated!'); }); ``` 在使用者被轉址後,你就能從 Session 取出訊息並顯示在畫面上。比如這裡透過 Blade 來呈現: ``` @if (session('status')) <div class="alert alert-success"> {{ session('status') }} </div> @endif ``` ### 轉址並帶著先前輸入資料 你可以使用 RedirectRespoonse 實例的 withInput() 來將當前請求的輸入資料存入Session,以便再回到先前驗證有錯誤表單時可做為預設值。 `return back()->withInput();` ## 其他回應類型 response() 幫助函式可用於生成其它類型的回應實例。當不傳入任何參數去呼叫 response() 時,會回傳 Illuminate\Contracts\Routing\ResponseFactory contract 的一個實現。這個契約提供了幾個用於生成回應的方法: ### 視圖回應 如果需要把視圖作為回應內容返回的同時,控制回應狀態和 headers,就需要呼叫 view() ``` return response() ->view('hello', $data, 200) ->header('Content-Type', $type); ``` 如果不需要傳遞自定義的 HTTP 狀態碼和 headers,還可以直接使用全域的 view() ### JSON 回應 json() 會自動將 header 的 Content-Type 設定為 application/json,同時使用 PHP 的 json_encode() 將傳入的陣列轉換為 JSON ``` return response()->json([ 'name' => 'Abigail', 'state' => 'CA', ]); ``` 假如你想要生成一個 JSONP 回應,你可以在 json() 後面再接一個 withCallback() ``` return response() ->json(['name' => 'Abigail', 'state' => 'CA']) ->withCallback($request->input('callback')); ``` ### 檔案下載 download() 可用於生成一個回應,強制用戶瀏覽器去下載指定路徑的檔案。 download() 將檔案名稱作為其第二個參數,它將作為用戶下載檔案後的新檔名。最後你可以傳遞 HTTP header 陣列作為其第三個參數: ``` return response()->download($pathToFile); return response()->download($pathToFile, $name, $headers); ``` >注意: > >用於管理檔案下載的 Symfony HttpFoundation 要求下載的檔案要有一個 ASCII 檔名。 #### 流下載 有時你可能希望將傳入操作的字串回應轉換為可下載回應,而不需要將之寫入磁碟。此時可以使用 streamDownload()。這個方法接受 Closure、檔案名稱和非必須提供的 header 陣列作為參數 ``` use App\Services\GitHub; return response()->streamDownload(function () { echo GitHub::api('repo') ->contents() ->readme('laravel', 'laravel')['contents']; }, 'laravel-readme.md'); ``` ### 檔案回應 file() 用於直接在用戶瀏覽器顯示一個圖片或 PDF 之類的檔案,而不是下載。這個方法接受檔案路徑作為第一個參數,header 陣列作為第二個參數 ``` return response()->file($pathToFile); return response()->file($pathToFile, $headers); ``` ## 回應宏 如果你想要定義一個自定義的可以在多個路由和控制器中重複使用的回應,可以使用 Response Facade 上的 macro() 。你一般會在服務供應器的 boot() 方法寫以下程式,尤其是在 App\Providers\AppServiceProvider ``` <?php namespace App\Providers; use Illuminate\Support\Facades\Response; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { Response::macro('caps', function ($value) { return Response::make(strtoupper($value)); }); } } ``` macro() 接受一個名稱作為第一個參數,Closure 作為第二個參數。回應宏的 Closure 在 ResponseFactory 實現類別或幫助函式 response() 中呼叫宏名稱的時候被執行 `return response()->caps('foo');`