產生 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
Accessing the Current URL
若未提供路徑給 url
輔助函式,則會回傳 Illuminate\Routing\UrlGenerator
實體,使用該實體能讓我們存取有關目前 URL 的資訊:
1// Get the current URL without the query string...2echo url()->current();34// Get the current URL including the query string...5echo url()->full();67// Get the full URL for the previous request...8echo url()->previous();
1// Get the current URL without the query string...2echo url()->current();34// Get the current URL including the query string...5echo url()->full();67// Get the full URL for the previous request...8echo url()->previous();
這些方法也可以通過 URL
Facade 來存取:
1use Illuminate\Support\Facades\URL;23echo URL::current();
1use Illuminate\Support\Facades\URL;23echo URL::current();
URLs for Named Routes
也可以使用 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]);
可以提供 absolute
引數給 signedRoute
方法來在 URL 簽名 Hash 中排除網域:
1return URL::signedRoute('unsubscribe', ['user' => 1], absolute: false);
1return URL::signedRoute('unsubscribe', ['user' => 1], absolute: false);
若想產生在指定時間後會過期的臨時簽名 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}
Instead of validating signed URLs using the incoming request instance, you may assign the signed
(Illuminate\Routing\Middleware\ValidateSignature
) middleware to the route. If the incoming request does not have a valid signature, the middleware will automatically return a 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');
若在簽名的 URL 中,URL Hash 不包含網域,則需要在 Middleware 中包含 relative
引數:
1Route::post('/unsubscribe/{user}', function (Request $request) {2 // ...3})->name('unsubscribe')->middleware('signed:relative');
1Route::post('/unsubscribe/{user}', function (Request $request) {2 // ...3})->name('unsubscribe')->middleware('signed:relative');
Responding to Invalid Signed Routes
When someone visits a signed URL that has expired, they will receive a generic error page for the 403
HTTP status code. However, you can customize this behavior by defining a custom "render" closure for the InvalidSignatureException
exception in your application's bootstrap/app.php
file:
1use Illuminate\Routing\Exceptions\InvalidSignatureException;23->withExceptions(function (Exceptions $exceptions) {4 $exceptions->render(function (InvalidSignatureException $e) {5 return response()->view('error.link-expired', [], 403);6 });7})
1use Illuminate\Routing\Exceptions\InvalidSignatureException;23->withExceptions(function (Exceptions $exceptions) {4 $exceptions->render(function (InvalidSignatureException $e) {5 return response()->view('error.link-expired', [], 403);6 });7})
URLs for Controller Actions
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\Http\Request;7use Illuminate\Support\Facades\URL;8use Symfony\Component\HttpFoundation\Response;910class SetDefaultLocaleForUrls11{12 /**13 * Handle an incoming request.14 *15 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next16 */17 public function handle(Request $request, Closure $next): Response18 {19 URL::defaults(['locale' => $request->user()->locale]);2021 return $next($request);22 }23}
1<?php23namespace App\Http\Middleware;45use Closure;6use Illuminate\Http\Request;7use Illuminate\Support\Facades\URL;8use Symfony\Component\HttpFoundation\Response;910class SetDefaultLocaleForUrls11{12 /**13 * Handle an incoming request.14 *15 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next16 */17 public function handle(Request $request, Closure $next): Response18 {19 URL::defaults(['locale' => $request->user()->locale]);2021 return $next($request);22 }23}
為 locale
參數設定好預設值後,使用 route
輔助函式產生 URL 時就不需要再傳入這個值了:
URL Defaults and Middleware Priority
Setting URL default values can interfere with Laravel's handling of implicit model bindings. Therefore, you should prioritize your middleware that set URL defaults to be executed before Laravel's own SubstituteBindings
middleware. You can accomplish this using the priority
middleware method in your application's bootstrap/app.php
file:
1->withMiddleware(function (Middleware $middleware) {2 $middleware->priority([3 \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,4 \Illuminate\Cookie\Middleware\EncryptCookies::class,5 \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,6 \Illuminate\Session\Middleware\StartSession::class,7 \Illuminate\View\Middleware\ShareErrorsFromSession::class,8 \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,9 \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,10 \Illuminate\Routing\Middleware\ThrottleRequests::class,11 \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,12 \Illuminate\Session\Middleware\AuthenticateSession::class,13 \App\Http\Middleware\SetDefaultLocaleForUrls::class,14 \Illuminate\Routing\Middleware\SubstituteBindings::class,15 \Illuminate\Auth\Middleware\Authorize::class,16 ]);17})
1->withMiddleware(function (Middleware $middleware) {2 $middleware->priority([3 \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,4 \Illuminate\Cookie\Middleware\EncryptCookies::class,5 \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,6 \Illuminate\Session\Middleware\StartSession::class,7 \Illuminate\View\Middleware\ShareErrorsFromSession::class,8 \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,9 \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,10 \Illuminate\Routing\Middleware\ThrottleRequests::class,11 \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,12 \Illuminate\Session\Middleware\AuthenticateSession::class,13 \App\Http\Middleware\SetDefaultLocaleForUrls::class,14 \Illuminate\Routing\Middleware\SubstituteBindings::class,15 \Illuminate\Auth\Middleware\Authorize::class,16 ]);17})