在生成站內鏈接、資源路徑、AJAX 請求 URL 時,需要一個**不含域名、以斜槓結尾**的基礎路徑(如 `/myapp/`)。本文提供通用、無框架依賴的獲取方案,解決子目錄部署、URL 重寫、HTTPS 判斷、CLI 環境等實際痛點。
SCRIPT_NAME 當前執行腳本相對文檔根目錄的路徑(如 `/myapp/index.php`、`/api/v1/user.php`)。取其目錄部分即為 Base Path。
<?php
/**
* 獲取 Base URL(相對域名根目錄,以 / 結尾)
* @return string 如 / /myapp/ /api/v1/
*/
function getBaseUrl(): string {
// 1. 獲取腳本路徑,統一斜槓
$scriptName = $_SERVER['SCRIPT_NAME'] ?? '';
$scriptName = str_replace('\\', '/', $scriptName); // Windows 兼容
// 2. 取目錄部分
$baseUrl = dirname($scriptName);
// 3. 標準化:空 → '/',去除首尾斜槓後再加斜槓
$baseUrl = '/' . trim($baseUrl, '/') . '/';
// 4. 特殊情況:CLI 模式無 SCRIPT_NAME
if (php_sapi_name() === 'cli') {
return '/'; // 或拋出異常,視業務而定
}
return $baseUrl;
}
// 使用
$baseUrl = getBaseUrl(); // /myapp/
$assetUrl = $baseUrl . 'assets/css/app.css'; // /myapp/assets/css/app.css
$apiUrl = $baseUrl . 'api/user'; // /myapp/api/user
?>
Nginx/Apache 反向代理到子路徑時,需傳遞前綴:
# Nginx
location /myapp/ {
proxy_pass https://backend/;
proxy_set_header X-Forwarded-Prefix /myapp;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
}
PHP 端優先使用代理頭:
<?php
function getBaseUrlAdvanced(): string {
// 1. 反向代理注入的前綴(最高優先級)
if (!empty($_SERVER['HTTP_X_FORWARDED_PREFIX'])) {
$prefix = rtrim($_SERVER['HTTP_X_FORWARDED_PREFIX'], '/');
return $prefix ? '/' . $prefix . '/' : '/';
}
// 2. 標準 SCRIPT_NAME
$scriptName = $_SERVER['SCRIPT_NAME'] ?? '';
$scriptName = str_replace('\\', '/', $scriptName);
$baseUrl = dirname($scriptName);
$baseUrl = '/' . trim($baseUrl, '/') . '/';
// 3. CLI 兼容
if (php_sapi_name() === 'cli') return '/';
return $baseUrl;
}
?>
<?php
class Url {
private static $baseUrl = null;
private static $scheme = null;
private static $host = null;
public static function base(): string {
return self::$baseUrl ??= self::detectBaseUrl();
}
public static function to(string $path): string {
$path = ltrim($path, '/');
return self::base() . $path;
}
public static function asset(string $path): string {
return self::to('assets/' . ltrim($path, '/'));
}
public static function full(string $path): string {
return self::scheme() . '://' . self::host() . self::to($path);
}
private static function detectBaseUrl(): string {
if (!empty($_SERVER['HTTP_X_FORWARDED_PREFIX'])) {
return '/' . trim($_SERVER['HTTP_X_FORWARDED_PREFIX'], '/') . '/';
}
$scriptName = str_replace('\\', '/', $_SERVER['SCRIPT_NAME'] ?? '');
return '/' . trim(dirname($scriptName), '/') . '/';
}
private static function scheme(): string {
return self::$scheme ??= (
(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
(!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
) ? 'https' : 'http';
}
private static function host(): string {
return self::$host ??= $_SERVER['HTTP_HOST'] ?? 'localhost';
}
}
// 使用
echo Url::base(); // /myapp/
echo Url::to('api/user'); // /myapp/api/user
echo Url::asset('css/app.css'); // /myapp/assets/css/app.css
echo Url::full('login'); // https://example.com/myapp/login
?>
| 框架 | 獲取 Base URL 方法 | 特點 |
|---|---|---|
| 原生 PHP (本文) | 自定義函數/類 | 零依賴、可控、適合微型項目 |
| Laravel | `url('/')` / `asset()` / `route()` | 基於 `APP_URL` 配置、自動處理代理、強大 |
| Symfony | `$request->getBasePath()` / `asset()` | Request 對象封裝、支持子請求、專業 |
| Yii 2 | `Yii::$app->request->baseUrl` / `Url::to()` | 組件化、自動檢測、Gii 生成代碼直接用 |
| ThinkPHP | `Request::instance()->baseUrl()` / `url()` | 單例模式、簡潔 |
| Zend/Laminas | `$this->url()` (View Helper) / `ServerUrlHelper` | 視圖助手、靈活但樣板代碼多 |
**最佳實踐**:新項目直接用框架內置 URL 生成器;老項目/微型項目引入本文 `Url` 類,統一管理路徑生成,避免硬編碼導致遷移地獄。