在建構模組載入器、資源引用或遷移腳本時,常需計算「檔案 B 相對於檔案 A 的相對路徑」。PHP 內建 realpath() 只能取得絕對路徑,本文提供一個健壯的 getRelativePath() 函式,並說明演算法邏輯與邊界處理。
/**
* 取得 $target 相對於 $base 的相對路徑
* @param string $base 基準檔案/目錄絕對路徑
* @param string $target 目標檔案/目錄絕對路徑
* @return string 相對路徑(如 ../../target/file.php)
*/
function getRelativePath($base, $target) {
// 統一路徑分隔符並移除尾斜線
$base = rtrim(str_replace('\\', '/', $base), '/');
$target = rtrim(str_replace('\\', '/', $target), '/');
// 分割路徑片段
$baseParts = explode('/', $base);
$targetParts = explode('/', $target);
// 移除檔名(保留目錄層級)
if (strpos($base, '.') !== false) array_pop($baseParts);
if (strpos($target, '.') !== false) array_pop($targetParts);
// 找出共同前綴
$commonLen = 0;
while ($commonLen < count($baseParts) && $commonLen < count($targetParts)
&& $baseParts[$commonLen] === $targetParts[$commonLen]) {
$commonLen++;
}
// 計算需向上層級數
$upLevels = count($baseParts) - $commonLen;
// 組合相對路徑
$rel = array_merge(
array_fill(0, $upLevels, '..'),
array_slice($targetParts, $commonLen)
);
return implode('/', $rel) ?: '.';
}
$tests = [
['/a/b/c/d/e.php', '/a/b/12/34/c.php', '../../12/34/c.php'],
['/var/www/html/index.php', '/var/www/config/app.php', '../config/app.php'],
['/home/user/project/src/Controller.php', '/home/user/project/public/index.php', '../../public/index.php'],
['/tmp/a/b', '/tmp/a/b', '.'],
['/tmp/a/b/', '/tmp/a/b/c', 'c'],
];
foreach ($tests as [$base, $target, $expect]) {
$result = getRelativePath($base, $target);
echo "Base: $base\nTarget: $target\nResult: $result " . ($result === $expect ? '✓' : "✗ (expect: $expect)") . "\n\n";
}
.. 的數量。// Windows 路徑
getRelativePath('C:\www\a\b', 'C:\www\c\d'); // ..\..\c\d
// URL 路徑(需先 parse_url 取 path)
$urlBase = 'https://example.com/a/b/';
$urlTarget = 'https://example.com/c/d';
getRelativePath(parse_url($urlBase, PHP_URL_PATH), parse_url($urlTarget, PHP_URL_PATH)); // ../c/d
spl_object_id 做鍵)。RelativePath 類(假設未來內建),目前仍需自行實作。此函式已處理尾斜線、檔名、跨盤符(Windows)、相同路徑等邊界情況,可直接納入專案工具庫。