給定兩個絕對路徑,計算從 $a 到 $b 的相對路徑,支援任意深度、跨目錄層級。
<?php
/**
* 計算 $b 相對於 $a 的相對路徑
* @param string $a 基準路徑 (檔案或目錄)
* @param string $b 目標路徑 (檔案或目錄)
* @return string 相對路徑
*/
function getRelativePath(string $a, string $b): string
{
// 統一處理為目錄路徑
$a = is_file($a) ? dirname($a) : rtrim($a, '/\\');
$b = is_file($b) ? dirname($b) : rtrim($b, '/\\');
// 路徑分段
$aParts = explode('/', $a);
$bParts = explode('/', $b);
// 找出共同前綴
$common = 0;
$minLen = min(count($aParts), count($bParts));
for ($i = 0; $i < $minLen; $i++) {
if ($aParts[$i] === $bParts[$i]) {
$common++;
} else {
break;
}
}
// 構建相對路徑
$upLevels = count($aParts) - $common;
$relative = str_repeat('../', $upLevels);
$relative .= implode('/', array_slice($bParts, $common));
return $relative ?: '.';
}
// 測試案例
$tests = [
['/var/www/project/a.php', '/var/www/project/config/db.php', 'config/db.php'],
['/var/www/project/src/Controller', '/var/www/project/public', '../../public'],
['/home/user/a/b/c', '/home/user/x/y', '../../../x/y'],
['C:/www/a/b', 'C:/www/x/y', '../../x/y'], // Windows 路徑亦適用
];
foreach ($tests as [$a, $b, $expected]) {
$result = getRelativePath($a, $b);
echo "A: $a\nB: $b\nExpected: $expected\nGot: $result\n" . ($result === $expected ? '✓' : '✗') . "\n\n";
}
?>
<?php
function getRelativePathAdvanced(string $from, string $to): string
{
// 解析 URL
if (strpos($from, '://') !== false) {
$from = parse_url($from, PHP_URL_PATH);
}
if (strpos($to, '://') !== false) {
$to = parse_url($to, PHP_URL_PATH);
}
// 正規化路徑分隔符
$from = str_replace('\\', '/', $from);
$to = str_replace('\\', '/', $to);
// 移除尾部斜線
$from = rtrim($from, '/');
$to = rtrim($to, '/');
// 如果是檔案路徑,取目錄
if (pathinfo($from, PATHINFO_EXTENSION)) $from = dirname($from);
if (pathinfo($to, PATHINFO_EXTENSION)) $to = dirname($to);
$fromParts = $from ? explode('/', $from) : [];
$toParts = $to ? explode('/', $to) : [];
// 計算共同前綴長度
$common = 0;
foreach ($fromParts as $i => $part) {
if (!isset($toParts[$i]) || $toParts[$i] !== $part) break;
$common++;
}
// 向上層級數
$up = count($fromParts) - $common;
// 向下路徑段
$down = array_slice($toParts, $common);
return str_repeat('../', $up) . implode('/', $down);
}
?>
<?php
// 使用 Symfony Polyfill 或原生 (需啟用)
function getRelativePathNative(string $from, string $to): string
{
// 將路徑轉為實際檔案系統路徑 (需檔案存在)
// $from = realpath($from);
// $to = realpath($to);
// 純字串計算可使用第三方庫:
// composer require phpstan/phpstan-path-resolver
// 或 symfony/filesystem
// 簡易版: 利用 SplFileInfo (僅限真實路徑)
// return (new SplFileInfo($to))->getRelativePath($from); // 不存在此方法
return getRelativePath($from, $to); // 使用上述自訂函數
}
?>
純字串運算無需檔案實際存在,效能極高,適合各類路徑轉換需求。