2010-04-14 3 views
37

Я заметил некоторые подобные вопросы об этой проблеме, когда я набрал заголовок, но они, похоже, не в PHP. Итак, каково решение этой проблемы с помощью функции PHP?Получение относительного пути от абсолютного пути в PHP

Указать.

$a="/home/apache/a/a.php"; 
$b="/home/root/b/b.php"; 
$relpath = getRelativePath($a,$b); //needed function,should return '../../root/b/b.php' 

Любые хорошие идеи? Благодарю.

+9

Каков ваш прецедент для использования относительного пути, когда у вас есть реальный путь? –

+0

Не могли бы вы опубликовать эти похожие вопросы? Написание порта для PHP проще, что все это заново. – Crozin

+2

@Tim Lytle, я думаю, что это может быть какой-то смысл, когда вы делаете какую-то ссылку/включаете материал. И также для интереса. – Young

ответ

60

Попробуйте это:

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); 
} 

Это даст

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

и

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

и

$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 

и

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

Похоже, мы придумали почти тот же алгоритм, мой по-английски, ваш в PHP (я слишком ленив, тоже код его, вы не являетесь). – webbiedave

+1

Я тестировал это и все остальные функции, и вы можете найти результаты моих тестов в моем прокрутке. Эта реализация оказалась лучшей, поэтому я предлагаю использовать ее! ;-) – lucaferrario

8

Относительный путь? Это больше похоже на путь путешествия. Кажется, вы хотите узнать путь, по которому отправляется путь от пути A к пути B. Если это так, вы можете explode $ a и $ b на '/', затем инвертируйте цикл через $ aParts, сравнивая их с $ bParts того же пока не будет найден каталог «общего знаменателя» (записывая количество циклов вдоль пути). Затем создайте пустую строку и добавьте '../' к ней $ numLoops-1 раз, затем добавьте к этому $ b минус общий каталог знаменателей.

2

на основе функции Гордона, мое решение заключается в следующем:

function getRelativePath($from, $to) 
{ 
    $from = explode('/', $from); 
    $to = explode('/', $to); 
    foreach($from as $depth => $dir) 
    { 

     if(isset($to[$depth])) 
     { 
      if($dir === $to[$depth]) 
      { 
       unset($to[$depth]); 
       unset($from[$depth]); 
      } 
      else 
      { 
       break; 
      } 
     } 
    } 
    //$rawresult = implode('/', $to); 
    for($i=0;$i<count($from)-1;$i++) 
    { 
     array_unshift($to,'..'); 
    } 
    $result = implode('/', $to); 
    return $result; 
} 
1

Некоторые Причина Гордон не работает для меня .... Вот мое решение

function getRelativePath($from, $to) { 
    $patha = explode('/', $from); 
    $pathb = explode('/', $to); 
    $start_point = count(array_intersect($patha,$pathb)); 
    while($start_point--) { 
     array_shift($patha); 
     array_shift($pathb); 
    } 
    $output = ""; 
    if(($back_count = count($patha))) { 
     while($back_count--) { 
      $output .= "../"; 
     } 
    } else { 
     $output .= './'; 
    } 
    return $output . implode('/', $pathb); 
} 
+0

Возможно, вы поместите путь к папке в $ из без косой черты. Эти функции от Gordon and Young требуют привязки к папкам. К сожалению, ваша функция работает с некоторыми путями, но не с некоторыми другими. Пожалуйста, прочитайте тест, который я сделал в другом посте. Эта функция не кажется надежной и не должна использоваться. – lucaferrario

1

Я пришел к такому же результату, используя эти манипуляции с массивами:

function getRelativePath($path, $from = __FILE__) 
{ 
    $path = explode(DIRECTORY_SEPARATOR, $path); 
    $from = explode(DIRECTORY_SEPARATOR, dirname($from.'.')); 
    $common = array_intersect_assoc($path, $from); 

    $base = array('.'); 
    if ($pre_fill = count(array_diff_assoc($from, $common))) { 
     $base = array_fill(0, $pre_fill, '..'); 
    } 
    $path = array_merge($base, array_diff_assoc($path, $common)); 
    return implode(DIRECTORY_SEPARATOR, $path); 
} 

Второй аргумент - это файл, к которому относится путь. Это необязательно, поэтому вы можете получить относительный путь независимо от веб-страницы, которую вы сейчас находитесь. Для того, чтобы использовать его с @Young или @Gordon например, потому что вы хотите знать относительный путь к $ Ь от $ а, вы должны будете использовать

getRelativePath($b, $a); 
+1

К сожалению, ваша функция работает с некоторыми путями, но не с некоторыми другими. Пожалуйста, прочитайте тест, который я сделал в другом посте. Эта функция не кажется надежной и не должна использоваться. Вместо этого я предлагаю использовать Gordon's, который всегда возвращает правильный результат. – lucaferrario

+0

@ lucaferrario, пожалуйста, проверьте мой [комментарий] (http://stackoverflow.com/questions/2637945/getting-relative-path-from-absolute-path-in-php/14329380#comment21288786_14878054) выше – loranger

13

Поскольку у нас было несколько ответов, Я решил проверить их все и сравнить их. Я использовал этот путь, чтобы испытать:

$from = "/var/www/sites/web/mainroot/webapp/folder/sub/subf/subfo/subfol/subfold/lastfolder/"; ПРИМЕЧАНИЕ: если это папка, вы должны положить завершающую черту для правильной работы функций! Итак, __DIR__ не будет работать.Используйте __FILE__ вместо или __DIR__ . '/'

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

РЕЗУЛЬТАТЫ: (десятичный разделитель запятая, тысячи разделитель точка)

  • Функция Гордон: результат ПРАВИЛЬНЫЙ, время для 100.000 Execs seconds
  • Функция by Young: результат C Дый правильный, время для 100.000 Execs +1540 секунд
  • Функция по Ceagle Результат: НЕПРАВИЛЬНО (она работает с некоторыми путями, но не с некоторыми другими, как те, которые используются в тестах и ​​написано выше)
  • функции по Loranger: результат НЕПРАВИЛЬНО (он работает с некоторыми путями, но не с некоторыми другими, как те, которые используются в тестах и ​​выписанных выше)

Итак, я предлагаю вам использовать реализацию Гордона! (тот, который отмечен как ответ)

Молодой тоже хорош и лучше работает с простыми структурами каталогов (например, «a/b/c.php»), в то время как Gordon's лучше работает со сложными структурами с большим количеством подкаталогов (как те, которые использовались в этом тесте).


Примечание: Я пишу здесь ниже результаты возвращаются с $from и $to в качестве входных данных, так что вы можете проверить, что 2 из них в порядке, в то время как другие 2 неверны:

  • Гордон: ../../../../../../aaa/bbb/ccc/ddd - -> ПРАВИЛЬНО
  • Young: ../../../../../../aaa/bbb/ccc/ddd -> ПРАВИЛЬНО
  • Ceagle: ../../../../../../bbb/ccc/ddd -> НЕПРАВИЛЬНО
  • Loranger: ../../../../../aaa/bbb/ccc/ddd -> НЕПРАВИЛЬНО
+0

Хороший тест. Вы тестировали файлы вместо файлов (по запросу), поэтому моя функция не смогла вернуть правильный путь. В любом случае, вы правы, чтобы быть надежным, эта функция всегда должна возвращать правильный путь, независимо от того, использует ли он файлы или папки, поэтому я просто исправил эту проблему. Мне было бы интересно получить мои результаты. Не могли бы вы провести сравнение в другой раз? – loranger

+1

Простите, что Loranger, но написал надежный сценарий бенчмаркинга, занял у меня некоторое время ... и, к сожалению, у меня его больше нет, и у меня нет времени писать еще один, чтобы повторить тест. Во всяком случае, если вы исправили проблему, молодцы!:-) – lucaferrario

3
const DS = DIRECTORY_SEPARATOR; // for convenience 

function getRelativePath($from, $to) { 
    $dir = explode(DS, is_file($from) ? dirname($from) : rtrim($from, DS)); 
    $file = explode(DS, $to); 

    while ($dir && $file && ($dir[0] == $file[0])) { 
     array_shift($dir); 
     array_shift($file); 
    } 
    return str_repeat('..'.DS, count($dir)) . implode(DS, $file); 
} 

Моя попытка не намеренно проще, хотя, вероятно, не отличается по производительности. Я оставлю бенчмаркинг как упражнение для любопытного читателя. Тем не менее, это довольно устойчиво и должно быть агностическим.

Опасайтесь решения, использующие функции array_intersect, поскольку они будут разбиты, если параллельные каталоги имеют одинаковое имя. Например, getRelativePath('start/A/end/', 'start/B/end/') вернет «../end», потому что array_intersect находит все равные имена, в этом случае 2, когда должно быть только 1.

+0

Это почти идентично решению, которое я только что придумал, за исключением немного более чистого с 'str_repeat' и' dirname'. Любить это! Благодарю. – mpen

2

Этот код взят из генератора URL Symfony https://github.com/symfony/Routing/blob/master/Generator/UrlGenerator.php

/** 
    * Returns the target path as relative reference from the base path. 
    * 
    * Only the URIs path component (no schema, host etc.) is relevant and must be given, starting with a slash. 
    * Both paths must be absolute and not contain relative parts. 
    * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. 
    * Furthermore, they can be used to reduce the link size in documents. 
    * 
    * Example target paths, given a base path of "https://stackoverflow.com/a/b/c/d": 
    * - "https://stackoverflow.com/a/b/c/d"  -> "" 
    * - "https://stackoverflow.com/a/b/c/"  -> "./" 
    * - "https://stackoverflow.com/a/b/"  -> "../" 
    * - "https://stackoverflow.com/a/b/c/other" -> "other" 
    * - "https://stackoverflow.com/a/x/y"  -> "../../x/y" 
    * 
    * @param string $basePath The base path 
    * @param string $targetPath The target path 
    * 
    * @return string The relative target path 
    */ 
    function getRelativePath($basePath, $targetPath) 
    { 
     if ($basePath === $targetPath) { 
      return ''; 
     } 

     $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); 
     $targetDirs = explode('/', isset($targetPath[0]) && '/' === $targetPath[0] ? substr($targetPath, 1) : $targetPath); 
     array_pop($sourceDirs); 
     $targetFile = array_pop($targetDirs); 

     foreach ($sourceDirs as $i => $dir) { 
      if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { 
       unset($sourceDirs[$i], $targetDirs[$i]); 
      } else { 
       break; 
      } 
     } 

     $targetDirs[] = $targetFile; 
     $path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs); 

     // A reference to the same base directory or an empty subdirectory must be prefixed with "./". 
     // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used 
     // as the first segment of a relative-path reference, as it would be mistaken for a scheme name 
     // (see http://tools.ietf.org/html/rfc3986#section-4.2). 
     return '' === $path || '/' === $path[0] 
      || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) 
      ? "./$path" : $path; 
    } 
0

Простой один-лайнер для общих сценариев:

str_replace(getcwd() . DIRECTORY_SEPARATOR, '', $filepath) 

или:

substr($filepath, strlen(getcwd())+1) 

Чтобы проверить, если путь абсолютный, попробуйте:

$filepath[0] == DIRECTORY_SEPARATOR 
0

Вот что работает для меня. По какой-то неизвестной причине, самый upvoted ответ на этот вопрос не работает, как ожидалось

public function getRelativePath($absolutePathFrom, $absolutePathDestination) 
{ 
    $absolutePathFrom = is_dir($absolutePathFrom) ? rtrim($absolutePathFrom, "\/")."/" : $absolutePathFrom; 
    $absolutePathDestination = is_dir($absolutePathDestination) ? rtrim($absolutePathDestination, "\/")."/" : $absolutePathDestination; 
    $absolutePathFrom = explode("/", str_replace("\\", "/", $absolutePathFrom)); 
    $absolutePathDestination = explode("/", str_replace("\\", "/", $absolutePathDestination)); 
    $relativePath = ""; 
    $path = array(); 
    $_key = 0; 
    foreach($absolutePathFrom as $key => $value) 
    { 
     if (strtolower($value) != strtolower($absolutePathDestination[$key])) 
     { 
      $_key = $key + 1; 
      for ($i = $key; $i < count($absolutePathDestination); $i++) 
      { 
       $path[] = $absolutePathDestination[$i]; 
      } 
      break; 
     } 
    } 
    for ($i = 0; $i <= (count($absolutePathFrom) - $_key - 1); $i++) 
    { 
     $relativePath .= "../"; 
    } 

    return $relativePath.implode("/", $path); 
} 

если $a = "C:\xampp\htdocs\projects\SMS\App\www\App\index.php" и
      $b = "C:\xampp\htdocs\projects\SMS\App/www/App/bin/bootstrap/css/bootstrap.min.css"

Тогда $c, что относительный путь $b от $a, будет

$c = getRelativePath($a, $b) = "bin/bootstrap/css/bootstrap.min.css"