こんにちはー!むちょこです。
今日は Laravel の route() ヘルパなどで自動生成される URL のルート(*)を強制的に指定する方法をご紹介します。リバースプロキシを使っているときなどに便利です♪
*) アプリケーションのトップページにあたる URL
手順だけ知りたい方は目次から飛んでください。
環境
この記事は Laravel 10.x で検証しながら書いています。
他の下位バージョンでもおそらく同様の手順で指定できると思いますが、未検証です。
Laravel の仕組み
まずは Laravel の中身がどうなっているのをざっくりとご紹介していきます。
Laravel の route() ヘルパをはじめとした URL 自動生成機能は、そのままだとサーバ変数を利用した値が使用されます。そのため、ブラウザのアドレスバーに表示されているルートと違う値でリンクが生成されることがあります。
ルートの取得
\Illuminate\Routing\UrlGenerator クラスの formatRoot() でルート URL が生成されています。
/**
* Get the base URL for the request.
*
* @param string $scheme
* @param string|null $root
* @return string
*/
public function formatRoot($scheme, $root = null)
{
if (is_null($root)) {
if (is_null($this->cachedRoot)) {
$this->cachedRoot = $this->forcedRoot ?: $this->request->root();
}
$root = $this->cachedRoot;
}
$start = str_starts_with($root, 'http://') ? 'http://' : 'https://';
return preg_replace('~'.$start.'~', $scheme, $root, 1);
}
ここで $this->forcedRoot (強制ルート)が指定されていなければ $this->request->root() の値が使用されます。
$this->request->root() の値は、\Symfony\Component\HttpFoundation\Request クラスの getSchemeAndHttpHost() からサーバ変数を元に取得しています。
強制ルートの設定
$this->forcedRoot の値は、同クラスの forceRootUrl() の第一引数に任意の値を渡すことで設定可能です。
/**
* Set the forced root URL.
*
* @param string|null $root
* @return void
*/
public function forceRootUrl($root)
{
$this->forcedRoot = $root ? rtrim($root, '/') : null;
$this->cachedRoot = null;
}
また、 \Illuminate\Routing\UrlGenerator クラスはファサードである \Illuminate\Support\Facades\URL クラスを通して手軽に利用することができます。
/**
*
* @method static void forceRootUrl(string|null $root)
*
* @see \Illuminate\Routing\UrlGenerator
*/
class URL extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'url';
}
}
手順
なんとなく仕組みが分かったところで、実際に forceRootUrl() を使って .env に設定した APP_URL の値を強制ルートにしていきます。
サービスプロバイダの実装
まずは artisan コマンドで URL に関するサービスプロバイダクラスを作成します。
$ ./vendor/bin/sail php artisan make:provider URLServiceProvider
INFO Provider [app/Providers/URLServiceProvider.php] created successfully.
次に、生成されたクラスで forceRootUrl() の引数に APP_URL の値を渡します。
APP_URL の値は env(‘APP_URL’) でも呼び出せますが、 config() を使ったほうが拡張性が高くてオススメです。
public function boot(): void
{
\Illuminate\Support\Facades\URL::forceRootUrl(config('app.url'));
}
最後に、 config/app.php の providers キーに URLServiceProvider クラスを追加したら実装完了です!
'providers' => [
App\Providers\UrlServiceProvider::class,
],
テスト
上記で実装した内容のテストコードは、簡易的ですがこんな感じで書きました。
まずは artisan コマンドでテストクラスを作成。
$ ./vendor/bin/sail php artisan make:test URLServiceProviderTest
INFO Test [tests/Feature/URLServiceProviderTest.php] created successfully.
route() で生成された URL と APP_URL で作成した URL が一致することを確認するコードを実装。
/**
* route() で生成された URL と APP_URL を使って作成した URL が同じ = URL Root は APP_URL と同じ値
*
* @return void
*/
public function test_url_root_is_the_same_value_as_app_url(): void
{
// \Illuminate\Routing\UrlGenerator::forceRootUrl() と同様の加工を行う
$app_url = rtrim(env('APP_URL'), '/');
$this->assertSame(route('login'), "{$app_url}/login");
}
テスト実行。
$ ./vendor/bin/sail php artisan test tests/Feature/URLServiceProviderTest.php
PASS Tests\Feature\URLServiceProviderTest
✓ url root is the same value as app url 0.15s
Tests: 1 passed (1 assertions)
Duration: 0.20s
無事テスト通りました!これで完成です!