產生 URL

簡介

Laravel 提供了多種輔助函式,來協助你為你的專案產生 URL。對於在樣板或 API 的 Response 中建立連結、或是產生要重新導向到網站中另一個部分的 Redirect Response 時特別實用。

基礎

產生 URL

可使用 url 輔助函式來為你的網站產生任意 URL。產生的 URL 會自動使用網站目前收到 Request 的配置(HTTP 或 HTTPS) 與主機名稱:

1$post = App\Models\Post::find(1);
2 
3echo url("/posts/{$post->id}");
4 
5// http://example.com/posts/1
1$post = App\Models\Post::find(1);
2 
3echo url("/posts/{$post->id}");
4 
5// http://example.com/posts/1

存取目前的 URL

若未提供路徑給 url 輔助函式,則會回傳 Illuminate\Routing\UrlGenerator 實體,使用該實體能讓我們存取有關目前 URL 的資訊:

1// 取得無 Query String 的目前 URL...
2echo url()->current();
3 
4// 取得含 Query String 的目前 URL...
5echo url()->full();
6 
7// 取得前一個 Request 的完整 URL...
8echo url()->previous();
1// 取得無 Query String 的目前 URL...
2echo url()->current();
3 
4// 取得含 Query String 的目前 URL...
5echo url()->full();
6 
7// 取得前一個 Request 的完整 URL...
8echo url()->previous();

這些方法也可以通過 URL Facade 來存取:

1use Illuminate\Support\Facades\URL;
2 
3echo URL::current();
1use Illuminate\Support\Facades\URL;
2 
3echo 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]);
2 
3// http://example.com/post/1
1echo route('post.show', ['post' => 1]);
2 
3// http://example.com/post/1

當然,也可以使用 route 輔助函式來為有多個參數的 Route 產生 URL:

1Route::get('/post/{post}/comment/{comment}', function (Post $post, Comment $comment) {
2 // ...
3})->name('comment.show');
4 
5echo route('comment.show', ['post' => 1, 'comment' => 3]);
6 
7// http://example.com/post/1/comment/3
1Route::get('/post/{post}/comment/{comment}', function (Post $post, Comment $comment) {
2 // ...
3})->name('comment.show');
4 
5echo route('comment.show', ['post' => 1, 'comment' => 3]);
6 
7// http://example.com/post/1/comment/3

若有陣列元素對應不上 Route 中定義的參數時,這些元素會被加到 URL 的查詢字串上:

1echo route('post.show', ['post' => 1, 'search' => 'rocket']);
2 
3// http://example.com/post/1?search=rocket
1echo route('post.show', ['post' => 1, 'search' => 'rocket']);
2 
3// 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;
2 
3return URL::signedRoute('unsubscribe', ['user' => 1]);
1use Illuminate\Support\Facades\URL;
2 
3return URL::signedRoute('unsubscribe', ['user' => 1]);

若想產生在指定時間後會過期的臨時簽名 Route URL,可以使用 temporarySignedRoute 方法。Laravel 在驗證臨時簽名 Route URL 時,也會確保被編碼進簽名 URL 中的過期時間時戳尚未到期:

1use Illuminate\Support\Facades\URL;
2 
3return URL::temporarySignedRoute(
4 'unsubscribe', now()->addMinutes(30), ['user' => 1]
5);
1use Illuminate\Support\Facades\URL;
2 
3return URL::temporarySignedRoute(
4 'unsubscribe', now()->addMinutes(30), ['user' => 1]
5);

驗證簽名 Route 的 Request

若要驗證連入 Request 是否有正確的簽名,可在連入的 Illuminate\Http\Request 實體上呼叫 hasValidSignature 方法:

1use Illuminate\Http\Request;
2 
3Route::get('/unsubscribe/{user}', function (Request $request) {
4 if (! $request->hasValidSignature()) {
5 abort(401);
6 }
7 
8 // ...
9})->name('unsubscribe');
1use Illuminate\Http\Request;
2 
3Route::get('/unsubscribe/{user}', function (Request $request) {
4 if (! $request->hasValidSignature()) {
5 abort(401);
6 }
7 
8 // ...
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 的 $middlewareAliases 陣列中為該 Middleware 設定一個索引鍵:

1/**
2 * The application's middleware aliases.
3 *
4 * Aliases may be used to conveniently assign middleware to routes and groups.
5 *
6 * @var array<string, class-string|string>
7 */
8protected $middlewareAliases = [
9 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
10];
1/**
2 * The application's middleware aliases.
3 *
4 * Aliases may be used to conveniently assign middleware to routes and groups.
5 *
6 * @var array<string, class-string|string>
7 */
8protected $middlewareAliases = [
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;
2 
3/**
4 * Register the exception handling callbacks for the application.
5 */
6public function register(): void
7{
8 $this->renderable(function (InvalidSignatureException $e) {
9 return response()->view('error.link-expired', [], 403);
10 });
11}
1use Illuminate\Routing\Exceptions\InvalidSignatureException;
2 
3/**
4 * Register the exception handling callbacks for the application.
5 */
6public function register(): void
7{
8 $this->renderable(function (InvalidSignatureException $e) {
9 return response()->view('error.link-expired', [], 403);
10 });
11}

Controller 動作的 URL

action 方法可為給定的 Controller 動作產生 URL:

1use App\Http\Controllers\HomeController;
2 
3$url = action([HomeController::class, 'index']);
1use App\Http\Controllers\HomeController;
2 
3$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<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Illuminate\Support\Facades\URL;
8use Symfony\Component\HttpFoundation\Response;
9 
10class SetDefaultLocaleForUrls
11{
12 /**
13 * Handle an incoming request.
14 *
15 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
16 */
17 public function handle(Request $request, Closure $next): Response
18 {
19 URL::defaults(['locale' => $request->user()->locale]);
20 
21 return $next($request);
22 }
23}
1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Illuminate\Support\Facades\URL;
8use Symfony\Component\HttpFoundation\Response;
9 
10class SetDefaultLocaleForUrls
11{
12 /**
13 * Handle an incoming request.
14 *
15 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
16 */
17 public function handle(Request $request, Closure $next): Response
18 {
19 URL::defaults(['locale' => $request->user()->locale]);
20 
21 return $next($request);
22 }
23}

locale 參數設定好預設值後,使用 route 輔助函式產生 URL 時就不需要再傳入這個值了:

URL 預設與 Middleware 的優先順序

設定 URL 的預設值可能會影響 Laravel 處理 Model 繫結。因此,請[調整 Middleware 的優先順序],讓設定 URL 預設的 Middleware 在 Laravel 的 SubstituteBindings 之前執行。可以通過在 HTTP Kernel 的 $middlewarePriority(/docs/master/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 array
7 */
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 array
7 */
8protected $middlewarePriority = [
9 // ...
10 \App\Http\Middleware\SetDefaultLocaleForUrls::class,
11 \Illuminate\Routing\Middleware\SubstituteBindings::class,
12 // ...
13];
翻譯進度
100% 已翻譯
更新時間:
2024年6月30日 上午8:27:00 [世界標準時間]
翻譯人員:
  • cornch
幫我們翻譯此頁

留言

尚無留言

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.