HTTP Response
建立 Response
Strings and Arrays
所有的 Route 與 Controller 都應回傳 Response,以傳送回使用者的瀏覽器。Laravel 中提供了多種不同的方法來回傳 Response。最基礎的 Response 就是從 Route 或 Controller 中回傳字串。Laravel 會自動將字串轉換為完整的 HTTP 回應:
1Route::get('/', function () {2 return 'Hello World';3});
1Route::get('/', function () {2 return 'Hello World';3});
除了從 Route 與 Controller 中回傳字串外,也可以回傳陣列。Laravel 會自動將陣列轉換為 JSON 回應:
1Route::get('/', function () {2 return [1, 2, 3];3});
1Route::get('/', function () {2 return [1, 2, 3];3});
你知道你也可以從 Route 或 Controller 中回傳 Eloquent Collection 嗎?回傳的 Eloquent Collection 會自動被轉為 JSON。試試看吧!
Response 物件
通常來說,我們不會只想在 Route 動作裡回傳簡單的字串或陣列。除了字串 / 陣列外,我們還可以回傳完整的 Illuminate\Http\Response
實體或 View。
若回傳完整的 Response
,就可以自訂回應的 HTTP 狀態碼與標頭。Response
實體繼承自 Symfony\Component\HttpFoundation\Response
類別,該類別提供各種不同的方法來建立 HTTP Response:
1Route::get('/home', function () {2 return response('Hello World', 200)3 ->header('Content-Type', 'text/plain');4});
1Route::get('/home', function () {2 return response('Hello World', 200)3 ->header('Content-Type', 'text/plain');4});
Eloquent Models and Collections
我們也可以從 Route 或 Controller 中回傳 Eloquent ORM Model 或 Collection。回傳 Eloquent Model 或 Collection 時,Laravel 會自動將其轉換為 JSON 回應。當 Model 上有隱藏屬性時,這些屬性也會被隱藏:
1use App\Models\User;23Route::get('/user/{user}', function (User $user) {4 return $user;5});
1use App\Models\User;23Route::get('/user/{user}', function (User $user) {4 return $user;5});
Attaching Headers to Responses
請記得,大多數的 Response 方法都是可串連的 (Chainable),讓我們能流暢地建構 Response 實體。舉例來說,我們可以在把 Respnse 傳回給使用者前使用 header
方法來加上一系列的標頭:
1return response($content)2 ->header('Content-Type', $type)3 ->header('X-Header-One', 'Header Value')4 ->header('X-Header-Two', 'Header Value');
1return response($content)2 ->header('Content-Type', $type)3 ->header('X-Header-One', 'Header Value')4 ->header('X-Header-Two', 'Header Value');
或者,我們也可以使用 withHeaders
方法來指定一組包含標頭的陣列,來將該陣列加到 Response 上:
1return response($content)2 ->withHeaders([3 'Content-Type' => $type,4 'X-Header-One' => 'Header Value',5 'X-Header-Two' => 'Header Value',6 ]);
1return response($content)2 ->withHeaders([3 'Content-Type' => $type,4 'X-Header-One' => 'Header Value',5 'X-Header-Two' => 'Header Value',6 ]);
快取 Controller Middleware
Laravel 中提供了一個 cache.headers
Middleware,可以使用該 Middleware 來快速將 Cache-Control
標頭設定到一組 Route 上。必須提供與 Cache-Control 指示詞 (Directive) 對應的「蛇形命名法 (snake_case)」指示詞,並使用分號區隔。若指示詞列表中有 etag
,則會自動以 Response 內容的 MD5 雜湊 (Hash) 來設定 ETag 識別元:
1Route::middleware('cache.headers:public;max_age=2628000;etag')->group(function () {2 Route::get('/privacy', function () {3 // ...4 });56 Route::get('/terms', function () {7 // ...8 });9});
1Route::middleware('cache.headers:public;max_age=2628000;etag')->group(function () {2 Route::get('/privacy', function () {3 // ...4 });56 Route::get('/terms', function () {7 // ...8 });9});
Attaching Cookies to Responses
可以使用 cookie
方法來將 Cookie 附加到外連的 Illuminate\Http\Response
實體。我們可以傳入 Cookie 的名稱、Cookie 值、以及單位為分鐘的有效期限給該方法:
1return response('Hello World')->cookie(2 'name', 'value', $minutes3);
1return response('Hello World')->cookie(2 'name', 'value', $minutes3);
cookie
方法還接受一些更多的引數,但這些引數很少用。一般來說,這些引數的功能跟 PHP 原生的 setcookie 方法一樣:
1return response('Hello World')->cookie(2 'name', 'value', $minutes, $path, $domain, $secure, $httpOnly3);
1return response('Hello World')->cookie(2 'name', 'value', $minutes, $path, $domain, $secure, $httpOnly3);
若想要與連出的 Response 一起送出 Cookie,但目前還未有 Response 實體的話,可使用 Cookie
Facade 來將 Cookie 「放到佇列」,以在 Response 送出的時候將其附加上去。queue
(佇列) 方法接受要用來建立 Cookie 實體的引數。這些佇列中的 Cookie 會在連出 Response 被送到瀏覽器前被附加上去:
1use Illuminate\Support\Facades\Cookie;23Cookie::queue('name', 'value', $minutes);
1use Illuminate\Support\Facades\Cookie;23Cookie::queue('name', 'value', $minutes);
產生 Cookie 實體
若想產生稍後可附加到 Response 實體上的 Symfony\Component\HttpFoundation\Cookie
實體,則可使用全域的 cookie
輔助函式。必須將產生的 Cookie 附加到 Response 實體,這些 Cookie 才會被送回用戶端:
1$cookie = cookie('name', 'value', $minutes);23return response('Hello World')->cookie($cookie);
1$cookie = cookie('name', 'value', $minutes);23return response('Hello World')->cookie($cookie);
提早讓 Cookie 過期
可以在連外 Response 上使用 withoutCookie
方法來讓 Cookie 無效,以將 Cookie 移除:
1return response('Hello World')->withoutCookie('name');
1return response('Hello World')->withoutCookie('name');
若還未有連外 Response 實體,則可以使用 Cookie
Facade 的 expire
方法來讓 Cookie 過期:
1Cookie::expire('name');
1Cookie::expire('name');
Cookies and Encryption
By default, thanks to the Illuminate\Cookie\Middleware\EncryptCookies
middleware, all cookies generated by Laravel are encrypted and signed so that they can't be modified or read by the client. If you would like to disable encryption for a subset of cookies generated by your application, you may use the encryptCookies
method in your application's bootstrap/app.php
file:
1->withMiddleware(function (Middleware $middleware) {2 $middleware->encryptCookies(except: [3 'cookie_name',4 ]);5})
1->withMiddleware(function (Middleware $middleware) {2 $middleware->encryptCookies(except: [3 'cookie_name',4 ]);5})
重新導向
Redirect Response (重新導向回應) 是 Illuminate\Http\RedirectResponse
類別的實體,Redirect Response 中包含了用來將使用者重新導向到另一個網址所需的一些標頭 (Header)。要產生 RedirectResponse
實體有幾個方法。最簡單的方法是使用全域的 redirect
輔助函式:
1Route::get('/dashboard', function () {2 return redirect('/home/dashboard');3});
1Route::get('/dashboard', function () {2 return redirect('/home/dashboard');3});
有時候(如:使用者送出了無效的表單時),我們可能會想把使用者重新導向到使用者瀏覽的前一個位置。為此,我們可以使用全域的 back
輔助函式。由於這個功能使用了 Session,因此請確保呼叫 back
函式的 Route 有使用 web
Middleware 群組:
1Route::post('/user/profile', function () {2 // Validate the request...34 return back()->withInput();5});
1Route::post('/user/profile', function () {2 // Validate the request...34 return back()->withInput();5});
Redirecting to Named Routes
呼叫 redirect
輔助函式時若沒有帶上任何參數,則會回傳 Illuminate\Routing\Redirector
實體,這樣我們就可以呼叫 Redirect
實體上的所有方法。舉例來說,若要為某個命名 Route 產生 RedirectResponse
,可以使用 route
方法:
1return redirect()->route('login');
1return redirect()->route('login');
若 Route 有參數,則可將這些 Route 參數作為第二個引數傳給 route
方法:
1// For a route with the following URI: /profile/{id}23return redirect()->route('profile', ['id' => 1]);
1// For a route with the following URI: /profile/{id}23return redirect()->route('profile', ['id' => 1]);
Populating Parameters via Eloquent Models
若要重新導向的 Route 中有個可從 Eloquent Model 中填充的「ID」參數,則可傳入 Model。會自動取出 ID:
1// For a route with the following URI: /profile/{id}23return redirect()->route('profile', [$user]);
1// For a route with the following URI: /profile/{id}23return redirect()->route('profile', [$user]);
若想自訂放在 Route 參數中的值,可在 Route 的參數定義中指定欄位 (/profile/{id:slug}
),或是在 Eloquent Model 中複寫 getRouteKey
方法:
1/**2 * Get the value of the model's route key.3 */4public function getRouteKey(): mixed5{6 return $this->slug;7}
1/**2 * Get the value of the model's route key.3 */4public function getRouteKey(): mixed5{6 return $this->slug;7}
Redirecting to Controller Actions
也可以產生一個前往 Controller 動作的重新導向。為此,請將 Controller 與動作名稱傳入 action
方法:
1use App\Http\Controllers\UserController;23return redirect()->action([UserController::class, 'index']);
1use App\Http\Controllers\UserController;23return redirect()->action([UserController::class, 'index']);
若這個 Controller 的 Route 有要求參數,則可將這些參數作為第二個引數傳給 action
方法:
1return redirect()->action(2 [UserController::class, 'profile'], ['id' => 1]3);
1return redirect()->action(2 [UserController::class, 'profile'], ['id' => 1]3);
Redirecting to External Domains
有時候,我們會需要重新導向到程式外部的網域。為此,可以呼叫 away
方法。該方法會建立一個 RedirectResponse
,並且不會做額外的 URL 編碼或驗證:
1return redirect()->away('https://www.google.com');
1return redirect()->away('https://www.google.com');
重新導向時帶上快閃存入的 Session 資料
通常,我們在重新導向到新網址的時候,也會[將資料快閃存入 Session]。一般來說,這種情況通常是當某個動作順利進行,而我們將成功訊息寫入 Session 時。為了方便起見,我們可以建立一個 RedirectResponse
實體,並以一行流暢的方法串連呼叫來將資料快閃存入 Session:
1Route::post('/user/profile', function () {2 // ...34 return redirect('/dashboard')->with('status', 'Profile updated!');5});
1Route::post('/user/profile', function () {2 // ...34 return redirect('/dashboard')->with('status', 'Profile updated!');5});
使用者被重新導向後,我們就可以從 Session 中顯示出剛才快閃存入的資料。舉例來說,我們可以使用 Blade 語法:
1@if (session('status'))2 <div class="alert alert-success">3 {{ session('status') }}4 </div>5@endif
1@if (session('status'))2 <div class="alert alert-success">3 {{ session('status') }}4 </div>5@endif
重新導向時帶上輸入
可以使用 RedirectResponse
實體提供的 withInput
方法來在將使用者重新導向到新位置前先將目前 Request 的輸入資料快閃存入 Session 中。通常來說我們會在表單驗證錯誤時這麼做。將輸入資料快閃存入 Session 後,我們就可以在下一個 Request 中輕鬆地取得這些資料並將其填回表單中:
1return back()->withInput();
1return back()->withInput();
其他 Response 類型
response
輔助函式還能產生一些其他類型的 Response 實體。呼叫 response
輔助函式時若未帶入任何引數,則會回傳 Illuminate\Contracts\Routing\ResponseFactory
Contract 的實作。這個 Contract 提供了數種用來建立 Response 的實用方法:
View Response
若有需要控制 Response 的狀態與標頭,但 Response 的內容又需要是 [View] 時,則可使用 view
方法:
1return response()2 ->view('hello', $data, 200)3 ->header('Content-Type', $type);
1return response()2 ->view('hello', $data, 200)3 ->header('Content-Type', $type);
當然,若不需要傳入自訂 HTTP 狀態或自訂標頭的話,應該使用全域的 view
輔助函式。
JSON Response
json
方法會自動將 Content-Type
標頭設為 application/json
,並使用 json_encode
PHP 函式來將任何給定的陣列轉換為 JSON:
1return response()->json([2 'name' => 'Abigail',3 'state' => 'CA',4]);
1return response()->json([2 'name' => 'Abigail',3 'state' => 'CA',4]);
若想建立 JSONP Response,則可在使用 json
方法時搭配使用 withCallback
方法:
1return response()2 ->json(['name' => 'Abigail', 'state' => 'CA'])3 ->withCallback($request->input('callback'));
1return response()2 ->json(['name' => 'Abigail', 'state' => 'CA'])3 ->withCallback($request->input('callback'));
檔案下載
可使用 download
方法來產生一個強制使用者在給定路徑上下載檔案的 Response。download
方法接受檔案名稱作為其第二個引數,該引數用來判斷使用者看到的檔案名稱。最後,我們可以傳入一組包含 HTTP 標頭的陣列作為該方法的第三個引數:
1return response()->download($pathToFile);23return response()->download($pathToFile, $name, $headers);
1return response()->download($pathToFile);23return response()->download($pathToFile, $name, $headers);
Symfony HttpFoundation —— 負責處理檔案下載的類別 —— 要求下載的檔案名稱必須為 ASCII。
File Response
The file
method may be used to display a file, such as an image or PDF, directly in the user's browser instead of initiating a download. This method accepts the absolute path to the file as its first argument and an array of headers as its second argument:
1return response()->file($pathToFile);23return response()->file($pathToFile, $headers);
1return response()->file($pathToFile);23return response()->file($pathToFile, $headers);
Streamed Responses
By streaming data to the client as it is generated, you can significantly reduce memory usage and improve performance, especially for very large responses. Streamed responses allow the client to begin processing data before the server has finished sending it:
1function streamedContent(): Generator {2 yield 'Hello, ';3 yield 'World!';4}56Route::get('/stream', function () {7 return response()->stream(function (): void {8 foreach (streamedContent() as $chunk) {9 echo $chunk;10 ob_flush();11 flush();12 sleep(2); // Simulate delay between chunks...13 }14 }, 200, ['X-Accel-Buffering' => 'no']);15});
1function streamedContent(): Generator {2 yield 'Hello, ';3 yield 'World!';4}56Route::get('/stream', function () {7 return response()->stream(function (): void {8 foreach (streamedContent() as $chunk) {9 echo $chunk;10 ob_flush();11 flush();12 sleep(2); // Simulate delay between chunks...13 }14 }, 200, ['X-Accel-Buffering' => 'no']);15});
Internally, Laravel utilizes PHP's output buffering functionality. As you can see in the example above, you should use the ob_flush
and flush
functions to push buffered content to the client.
Streamed JSON Responses
If you need to stream JSON data incrementally, you may utilize the streamJson
method. This method is especially useful for large datasets that need to be sent progressively to the browser in a format that can be easily parsed by JavaScript:
1use App\Models\User;23Route::get('/users.json', function () {4 return response()->streamJson([5 'users' => User::cursor(),6 ]);7});
1use App\Models\User;23Route::get('/users.json', function () {4 return response()->streamJson([5 'users' => User::cursor(),6 ]);7});
串流下載
有時候,我們會需要在不寫入磁碟的情況下將某個操作的字串結果轉變成可下載的 Response。這時可以使用 streamDownload
方法。這個方法接受一個回呼、檔案名稱、以及一個可選的標頭陣列作為其引數:
1use App\Services\GitHub;23return response()->streamDownload(function () {4 echo GitHub::api('repo')5 ->contents()6 ->readme('laravel', 'laravel')['contents'];7}, 'laravel-readme.md');
1use App\Services\GitHub;23return response()->streamDownload(function () {4 echo GitHub::api('repo')5 ->contents()6 ->readme('laravel', 'laravel')['contents'];7}, 'laravel-readme.md');
Response Macro
若想定義可在各個 Route 或 Controller 內重複使用的自訂 Response 方法,可使用 Response
Facade 上的 macro
方法。通常來說,該方法應在某個 Service Provider 的 boot
方法內呼叫,如 App\Providers\AppServiceProvider
Service Provider:
1<?php23namespace App\Providers;45use Illuminate\Support\Facades\Response;6use Illuminate\Support\ServiceProvider;78class AppServiceProvider extends ServiceProvider9{10 /**11 * Bootstrap any application services.12 */13 public function boot(): void14 {15 Response::macro('caps', function (string $value) {16 return Response::make(strtoupper($value));17 });18 }19}
1<?php23namespace App\Providers;45use Illuminate\Support\Facades\Response;6use Illuminate\Support\ServiceProvider;78class AppServiceProvider extends ServiceProvider9{10 /**11 * Bootstrap any application services.12 */13 public function boot(): void14 {15 Response::macro('caps', function (string $value) {16 return Response::make(strtoupper($value));17 });18 }19}
macro
方法接受一個名稱作為其第一個引數,以及閉包作為其第二個引數。當在 ResponseFactory
的實作或 response
輔助函式上呼叫給定的 Macro 名稱時,會執行該 Macro 的閉包:
1return response()->caps('foo');
1return response()->caps('foo');