產生 URL
簡介
Laravel 提供了多種輔助函式,來協助你為你的專案產生 URL。對於在樣板或 API 的 Response 中建立連結、或是產生要重新導向到網站中另一個部分的 Redirect Response 時特別實用。
基礎
產生 URL
可使用 url
輔助函式來為你的網站產生任意 URL。產生的 URL 會自動使用網站目前收到 Request 的配置(HTTP 或 HTTPS) 與主機名稱:
1$post = App\Models\Post::find(1);23echo url("/posts/{$post->id}");45// http://example.com/posts/1
1$post = App\Models\Post::find(1);23echo url("/posts/{$post->id}");45// http://example.com/posts/1
存取目前的 URL
若未提供路徑給 url
輔助函式,則會回傳 Illuminate\Routing\UrlGenerator
實體,使用該實體能讓我們存取有關目前 URL 的資訊:
1// 取得無 Query String 的目前 URL...2echo url()->current();34// 取得含 Query String 的目前 URL...5echo url()->full();67// 取得前一個 Request 的完整 URL...8echo url()->previous();
1// 取得無 Query String 的目前 URL...2echo url()->current();34// 取得含 Query String 的目前 URL...5echo url()->full();67// 取得前一個 Request 的完整 URL...8echo url()->previous();
這些方法也可以通過 URL
Facade 來存取:
1use Illuminate\Support\Facades\URL;23echo URL::current();
1use Illuminate\Support\Facades\URL;23echo URL::current();
命名 Route 的 URL
也可以使用 route
輔助函式來產生命名 Route的 URL。使用命名 Route 能讓我們不需要耦合到 Route 上實際定義的 URL,就能產生 URL。因此,即使 Route 的 URL 更改了,我們也不需要修改 route
函式的呼叫。舉例來說,假設我們的專案中有像這樣定義的 Route:
1Route::get('/post/{post}', function (Post $post) {2 //3})->name('post.show');
1Route::get('/post/{post}', function (Post $post) {2 //3})->name('post.show');
若要產生這個 Route 的 URL,可以像這樣使用 route
輔助函式:
1echo route('post.show', ['post' => 1]);23// http://example.com/post/1
1echo route('post.show', ['post' => 1]);23// http://example.com/post/1
當然,也可以使用 route
輔助函式來為有多個參數的 Route 產生 URL:
1Route::get('/post/{post}/comment/{comment}', function (Post $post, Comment $comment) {2 //3})->name('comment.show');45echo route('comment.show', ['post' => 1, 'comment' => 3]);67// http://example.com/post/1/comment/3
1Route::get('/post/{post}/comment/{comment}', function (Post $post, Comment $comment) {2 //3})->name('comment.show');45echo route('comment.show', ['post' => 1, 'comment' => 3]);67// http://example.com/post/1/comment/3
若有陣列元素對應不上 Route 中定義的參數時,這些元素會被加到 URL 的查詢字串上:
1echo route('post.show', ['post' => 1, 'search' => 'rocket']);23// http://example.com/post/1?search=rocket
1echo route('post.show', ['post' => 1, 'search' => 'rocket']);23// http://example.com/post/1?search=rocket
Eloquent Model
我們常常會使用 Eloquent Model 的 Route 索引鍵 (通常是主索引鍵 - Primary Key) 來產生 URL。因此,我們也可以將 Eloquent Model 作為參數值傳入。route
輔助函式會自動取出 Model 的 Route 索引鍵:
1echo route('post.show', ['post' => $post]);
1echo route('post.show', ['post' => $post]);
簽名 URL
Laravel 能讓我們輕鬆地為命名 Route 建立「簽名的 (Signed)」URL。這種 URL 的查詢字串中有個「簽名」雜湊,能讓 Laravel 驗證這個 URL 建立後是否有被修改。簽名的 URL 特別適用於一些可公開存取但又需要保護網址不被任意修改的 Route。
舉例來說,我們可以使用簽名 URL 來實作公開「解除訂閱」的連結,這個連結會寄給使用者。若要為命名路由建立簽名 URL,可使用 URL
Facade 的 signedRoute
方法:
1use Illuminate\Support\Facades\URL;23return URL::signedRoute('unsubscribe', ['user' => 1]);
1use Illuminate\Support\Facades\URL;23return URL::signedRoute('unsubscribe', ['user' => 1]);
若想產生在指定時間後會過期的臨時簽名 Route URL,可以使用 temporarySignedRoute
方法。Laravel 在驗證臨時簽名 Route URL 時,也會確保被編碼進簽名 URL 中的過期時間時戳尚未到期:
1use Illuminate\Support\Facades\URL;23return URL::temporarySignedRoute(4 'unsubscribe', now()->addMinutes(30), ['user' => 1]5);
1use Illuminate\Support\Facades\URL;23return URL::temporarySignedRoute(4 'unsubscribe', now()->addMinutes(30), ['user' => 1]5);
驗證簽名 Route 的 Request
若要驗證連入 Request 是否有正確的簽名,可在連入的 Illuminate\Http\Request
實體上呼叫 hasValidSignature
方法:
1use Illuminate\Http\Request;23Route::get('/unsubscribe/{user}', function (Request $request) {4 if (! $request->hasValidSignature()) {5 abort(401);6 }78 // ...9})->name('unsubscribe');
1use Illuminate\Http\Request;23Route::get('/unsubscribe/{user}', function (Request $request) {4 if (! $request->hasValidSignature()) {5 abort(401);6 }78 // ...9})->name('unsubscribe');
有時候,我們可能要讓程式的前端將資料附加到簽名 URL 上,例如在用戶端上做分頁時。因此,我們可以使用 hasValidSignatureWhileIgnoring
來指定哪些查詢參數在驗證簽名 URL 要被忽略不驗證。但請記得,忽略一個參數就能讓任何人都能修改這個參數:
1if (! $request->hasValidSignatureWhileIgnoring(['page', 'order'])) {2 abort(401);3}
1if (! $request->hasValidSignatureWhileIgnoring(['page', 'order'])) {2 abort(401);3}
除了使用連入 Request 實體來驗證簽名 URL 外,也可以將 Illuminate\Routing\Middleware\ValidateSignature
Middleware 指派給 Route。若該 Middleware 不存在,請在 HTTP Kernel 的 routeMiddleware
陣列中為該 Middleware 設定一個索引鍵:
1/**2 * The application's route middleware.3 *4 * These middleware may be assigned to groups or used individually.5 *6 * @var array7 */8protected $routeMiddleware = [9 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,10];
1/**2 * The application's route middleware.3 *4 * These middleware may be assigned to groups or used individually.5 *6 * @var array7 */8protected $routeMiddleware = [9 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,10];
在 Kernel 中註冊好 Middleware 後,就可以將其附加到 Route 上。若連入的 Request 沒有正確的簽名,該 Middleware 會自動回傳一個 403
HTTP Response:
1Route::post('/unsubscribe/{user}', function (Request $request) {2 // ...3})->name('unsubscribe')->middleware('signed');
1Route::post('/unsubscribe/{user}', function (Request $request) {2 // ...3})->name('unsubscribe')->middleware('signed');
回應無效簽名的 Route
若有人瀏覽了過期的簽名 URL,則會看到 403
HTTP 狀態碼通用的錯誤頁面。不過,我們也可以在我們的例外處理常式 (Exception Handler) 上為 InvalidSignatureException
例外定義一個自訂的「renderable (可轉譯的)」閉包來自訂此行為。這個閉包應回傳 HTTP Response:
1use Illuminate\Routing\Exceptions\InvalidSignatureException;23/**4 * Register the exception handling callbacks for the application.5 *6 * @return void7 */8public function register()9{10 $this->renderable(function (InvalidSignatureException $e) {11 return response()->view('error.link-expired', [], 403);12 });13}
1use Illuminate\Routing\Exceptions\InvalidSignatureException;23/**4 * Register the exception handling callbacks for the application.5 *6 * @return void7 */8public function register()9{10 $this->renderable(function (InvalidSignatureException $e) {11 return response()->view('error.link-expired', [], 403);12 });13}
Controller 動作的 URL
action
方法可為給定的 Controller 動作產生 URL:
1use App\Http\Controllers\HomeController;23$url = action([HomeController::class, 'index']);
1use App\Http\Controllers\HomeController;23$url = action([HomeController::class, 'index']);
若該 Controller 方法接受 Route 參數,則可將 Route 參數的關聯式陣列作為第二個引數傳給給函式:
1$url = action([UserController::class, 'profile'], ['id' => 1]);
1$url = action([UserController::class, 'profile'], ['id' => 1]);
預設值
在某個專案中,我們可能會想為特定的 URL 參數設定 Request 層級的預設值。舉例來說,假設我們的 Route 中很多都定義了 {locale}
參數:
1Route::get('/{locale}/posts', function () {2 //3})->name('post.index');
1Route::get('/{locale}/posts', function () {2 //3})->name('post.index');
若每次呼叫 route
輔助函式都要傳入 locale
的話會很麻煩。因此。我們可以使用 URL::defaults
方法來為這個參數定義目前 Request 中要套用的預設值。建議在某個 Route Middleware 中呼叫這個方法,這樣我們才能存取目前的 Request:
1<?php23namespace App\Http\Middleware;45use Closure;6use Illuminate\Support\Facades\URL;78class SetDefaultLocaleForUrls9{10 /**11 * Handle the incoming request.12 *13 * @param \Illuminate\Http\Request $request14 * @param \Closure $next15 * @return \Illuminate\Http\Response16 */17 public function handle($request, Closure $next)18 {19 URL::defaults(['locale' => $request->user()->locale]);2021 return $next($request);22 }23}
1<?php23namespace App\Http\Middleware;45use Closure;6use Illuminate\Support\Facades\URL;78class SetDefaultLocaleForUrls9{10 /**11 * Handle the incoming request.12 *13 * @param \Illuminate\Http\Request $request14 * @param \Closure $next15 * @return \Illuminate\Http\Response16 */17 public function handle($request, Closure $next)18 {19 URL::defaults(['locale' => $request->user()->locale]);2021 return $next($request);22 }23}
為 locale
參數設定好預設值後,使用 route
輔助函式產生 URL 時就不需要再傳入這個值了:
URL 預設與 Middleware 的優先順序
設定 URL 的預設值可能會影響 Laravel 處理 Model 繫結。因此,請[調整 Middleware 的優先順序],讓設定 URL 預設的 Middleware 在 Laravel 的 SubstituteBindings
之前執行。可以通過在 HTTP Kernel 的 $middlewarePriority
(/docs/9.x/middleware#sorting-middleware) 中將你的 Middleware 放在 SubstituteBindings
之前來達成。
Illuminate\Foundation\Http\Kernel
類別中定義了 $middlewarePriority
屬性。我們可以手動從該類別中複製這個定義並在專案的 HTTP Kernel 中複寫該屬性來修改其值:
1/**2 * The priority-sorted list of middleware.3 *4 * This forces non-global middleware to always be in the given order.5 *6 * @var array7 */8protected $middlewarePriority = [9 // ...10 \App\Http\Middleware\SetDefaultLocaleForUrls::class,11 \Illuminate\Routing\Middleware\SubstituteBindings::class,12 // ...13];
1/**2 * The priority-sorted list of middleware.3 *4 * This forces non-global middleware to always be in the given order.5 *6 * @var array7 */8protected $middlewarePriority = [9 // ...10 \App\Http\Middleware\SetDefaultLocaleForUrls::class,11 \Illuminate\Routing\Middleware\SubstituteBindings::class,12 // ...13];