在 Laravel 的 subdomain route 裡,url('test') 回傳的是 https://foo.com/test。
但如果你要產出主站的連結,例如寄 email 給使用者、或 API response 裡要帶回主站網址,這樣就錯了。
clone 一份 UrlGenerator 再 useOrigin(),三行解決。
問題
Route 設定了 subdomain:
1
2
3
4
5
6
7
| Route::middleware([])->domain('foo.com')->group(function () {
Route::get('/test', function () {
return [
'current' => url('test'), // 產出 https://foo.com/test
];
});
});
|
在這個 context 裡,url() 和 route() 都會產出帶 subdomain 的網址。這是 Laravel 的預期行為,但如果你需要的是主站(app.url)的網址,就得繞過去。
為什麼不能直接改
app(UrlGenerator::class) 從容器拿到的是 singleton。直接改它的 origin 會影響到這個 request 裡所有後續的 url() 呼叫,包括 middleware、response 等等。這是副作用,不能這樣做。
解法:clone 再 useOrigin
1
2
3
4
5
6
7
8
9
10
11
12
| Route::get('/test', function () {
// clone 一份,不動到原本的 singleton
$urlGenerator = clone app(UrlGenerator::class);
// 把 clone 的 origin 指向 app.url(主站)
$urlGenerator->useOrigin(config('app.url'));
return [
'origin' => $urlGenerator->to('test'), // https://localhost/test(主站)
'current' => url('test'), // https://foo.com/test(subdomain,沒被動到)
];
});
|
輸出:
1
2
3
4
| {
"origin": "https://localhost/test",
"current": "https://foo.com/test"
}
|
clone 讓兩個 UrlGenerator 互相獨立,useOrigin() 只影響那份 clone。原本的 url() helpers 完全沒被動到。
在測試裡驗證
寫測試的時候可以用 dump() 直接看輸出確認:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| it('subdomain route 產出正確的網址', function () {
Route::middleware([])->domain('foo.com')->group(function () {
Route::get('/test', function () {
$urlGenerator = clone app(UrlGenerator::class);
$urlGenerator->useOrigin(config('app.url'));
return [
'origin' => $urlGenerator->to('test'),
'current' => url('test'),
];
})->name('domain.test');
});
$response = getJson('https://foo.com/test');
$response->assertJson([
'origin' => 'https://localhost/test',
'current' => 'https://foo.com/test',
]);
});
|
實際應用場景
寄 Email:使用者在 tenant.app.com 操作,但信件裡的連結要指向 app.com 主站。
1
2
3
4
5
6
7
8
9
10
| // 在 subdomain 的 controller 裡
public function sendWelcomeEmail(User $user): void
{
$urlGenerator = clone app(UrlGenerator::class);
$urlGenerator->useOrigin(config('app.url'));
$loginUrl = $urlGenerator->route('login'); // https://app.com/login,不帶 subdomain
Mail::to($user)->send(new WelcomeMail($loginUrl));
}
|
API response 裡的連結:multi-tenant 架構,API 回傳的 canonical URL 或 redirect 連結要是主站網址而不是 tenant 的 subdomain。
useOrigin 做了什麼
useOrigin() 是 Laravel UrlGenerator 的方法,設定產出網址時使用的 scheme 和 host。它不改變 route 的 domain 設定,只改變最終組出來的 URL 字串的 origin 部分。
1
2
3
4
5
6
7
| // Laravel 原始碼的效果類似這樣
public function useOrigin(string $origin): static
{
[$this->forceScheme, $host] = explode('://', $origin);
$this->forceRootUrl($origin);
return $this;
}
|
clone 確保這個改動只活在這份 instance 裡,用完就消失,不污染全域狀態。
小結
遇到 subdomain route 裡需要產出 root domain 網址,clone app(UrlGenerator::class) 再 useOrigin() 是最乾淨的做法。不需要暫時改 config、不需要 URL::forceRootUrl()(那個是全域的),也不用自己拼字串。
參考資源