Getting relative path from absolute path in PHP

Solution 1:

Try this one:

function getRelativePath($from, $to)
{
    // some compatibility fixes for Windows paths
    $from = is_dir($from) ? rtrim($from, '\/') . '/' : $from;
    $to   = is_dir($to)   ? rtrim($to, '\/') . '/'   : $to;
    $from = str_replace('\\', '/', $from);
    $to   = str_replace('\\', '/', $to);

    $from     = explode('/', $from);
    $to       = explode('/', $to);
    $relPath  = $to;

    foreach($from as $depth => $dir) {
        // find first non-matching dir
        if($dir === $to[$depth]) {
            // ignore this directory
            array_shift($relPath);
        } else {
            // get number of remaining dirs to $from
            $remaining = count($from) - $depth;
            if($remaining > 1) {
                // add traversals up to first matching dir
                $padLength = (count($relPath) + $remaining - 1) * -1;
                $relPath = array_pad($relPath, $padLength, '..');
                break;
            } else {
                $relPath[0] = './' . $relPath[0];
            }
        }
    }
    return implode('/', $relPath);
}

This will give

$a="/home/a.php";
$b="/home/root/b/b.php";
echo getRelativePath($a,$b), PHP_EOL;  // ./root/b/b.php

and

$a="/home/apache/a/a.php";
$b="/home/root/b/b.php";
echo getRelativePath($a,$b), PHP_EOL; // ../../root/b/b.php

and

$a="/home/root/a/a.php";
$b="/home/apache/htdocs/b/en/b.php";
echo getRelativePath($a,$b), PHP_EOL; // ../../apache/htdocs/b/en/b.php

and

$a="/home/apache/htdocs/b/en/b.php";
$b="/home/root/a/a.php";
echo getRelativePath($a,$b), PHP_EOL; // ../../../../root/a/a.php

Solution 2:

Since we've had several answers, I decided to test them all and benchmark them. I used this paths to test:

$from = "/var/www/sites/web/mainroot/webapp/folder/sub/subf/subfo/subfol/subfold/lastfolder/"; NOTE: if it is a folder, you have to put a trailing slash for functions to work correctly! So, __DIR__ will not work. Use __FILE__ instead or __DIR__ . '/'

$to = "/var/www/sites/web/mainroot/webapp/folder/aaa/bbb/ccc/ddd";

RESULTS: (decimal separator is comma, thousand separator is dot)

  • Function by Gordon: result CORRECT, time for 100.000 execs 1,222 seconds
  • Function by Young: result CORRECT, time for 100.000 execs 1,540 seconds
  • Function by Ceagle: result WRONG (it works with some paths but fails with some others, like the ones used in the tests and written above)
  • Function by Loranger: result WRONG (it works with some paths but fails with some others, like the ones used in the tests and written above)

So, I suggest that you use Gordon's implementation! (the one marked as answer)

Young's one is good too and performs better with simple directory structures (like "a/b/c.php"), while Gordon's one performs better with complex structures, with lots of subdirectories (like the ones used in this benchmark).


NOTE: I write here below the results returned with $from and $to as inputs, so you can verify that 2 of them are OK, while other 2 are wrong:

  • Gordon: ../../../../../../aaa/bbb/ccc/ddd --> CORRECT
  • Young: ../../../../../../aaa/bbb/ccc/ddd --> CORRECT
  • Ceagle: ../../../../../../bbb/ccc/ddd --> WRONG
  • Loranger: ../../../../../aaa/bbb/ccc/ddd --> WRONG