題目
$a = '/a/b/c/d/e.php';
$b = '/a/b/12/34/c.php';
// 求 $b 相對於 $a 的相對路徑 → ../../c/d
演算法邏輯
- 以
/ 分割路徑為陣列
- 移除檔名部分 (最後一段)
- 從根目錄開始比對共同前綴
- $a 剩餘層級數 → 對應
../ 數量
- $b 剩餘層級 → 組成目標路徑
函數實作
<?php
/**
* 計算 $from 到 $to 的相對路徑
*
* @param string $from 來源絕對路徑 (含檔名)
* @param string $to 目標絕對路徑 (含檔名)
* @return string 相對路徑
*/
function getRelativePath($from, $to)
{
// 正規化:統一分隔符、移除重複斜線
$from = preg_replace('#/+#', '/', $from);
$to = preg_replace('#/+#', '/', $to);
// 分割並移除檔名
$fromParts = explode('/', rtrim($from, '/'));
$toParts = explode('/', rtrim($to, '/'));
array_pop($fromParts); // 移除 e.php
array_pop($toParts); // 移除 c.php
// 找共同前綴長度
$common = 0;
$minLen = min(count($fromParts), count($toParts));
while ($common < $minLen && $fromParts[$common] === $toParts[$common]) {
$common++;
}
// $from 需往上幾層
$up = count($fromParts) - $common;
// $to 從共同祖先往下的路徑
$down = array_slice($toParts, $common);
$rel = str_repeat('../', $up) . implode('/', $down);
return $rel ?: '.';
}
?>
測試驗證
<?php
$a = '/a/b/c/d/e.php';
$b = '/a/b/12/34/c.php';
echo getRelativePath($a, $b); // 輸出: ../../c/d
// 更多測試
echo getRelativePath('/a/b/c.php', '/a/b/d.php'); // d.php
echo getRelativePath('/a/b/c/d.php', '/a/e/f.php'); // ../../e/f
echo getRelativePath('/a/b/c.php', '/a/b/c/d.php'); // c/d
?>
邊界情況處理
- 相同路徑 → 回傳
.
- 跨磁碟機 (Windows) → 直接回傳絕對路徑或拋出異常
- 輸入非絕對路徑 → 先
realpath() 正規化
現代替代:Symfony Path / League URI
<?php
use Symfony\Component\Filesystem\Path;
$rel = Path::makeRelative($b, dirname($a));
// 需 composer require symfony/filesystem
?>