Получение относительного пути от абсолютного пути в 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' 

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

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

 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 

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

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

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

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

  • Функция Gordon: результат CORRECT , время для 100.000 execs 1,222 секунды
  • Функция by Young: результат CORRECT , время для 100.000 execs 1,540 секунд
  • Функция by Ceagle: результат WRONG (он работает с некоторыми путями, но не работает с некоторыми другими, такими как те, которые использовались в тестах и ​​написаны выше)
  • Функция Loranger: результат WRONG (он работает с некоторыми путями, но не работает с некоторыми другими, как те, которые использовались в тестах и ​​написаны выше)

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

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


ПРИМЕЧАНИЕ. Я пишу здесь ниже результатов, возвращаемых с помощью $from и $to качестве входных данных, поэтому вы можете проверить, что 2 из них в порядке, в то время как другие 2 неверны:

  • Гордон: ../../../../../../aaa/bbb/ccc/ddd -> ПРАВИЛЬНО
  • Young: ../../../../../../aaa/bbb/ccc/ddd -> CORRECT
  • Цагле: ../../../../../../bbb/ccc/ddd -> НЕПРАВИЛЬНО
  • Лорангер: ../../../../../aaa/bbb/ccc/ddd -> WRONG

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

 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 поскольку они будут array_intersect если параллельные каталоги имеют одинаковое имя. Например, getRelativePath('start/A/end/', 'start/B/end/') вернет « ../end », потому что array_intersect находит все одинаковые имена, в этом случае 2, когда должно быть только 1.

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

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

Этот код взят из генератора 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 "/a/b/c/d": * - "/a/b/c/d" -> "" * - "/a/b/c/" -> "./" * - "/a/b/" -> "../" * - "/a/b/c/other" -> "other" * - "/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 (eg, "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; } 

Некоторый разум Гордон не работал для меня … Вот мое решение

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

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

 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, потому что вы хотите знать относительный путь к $ b из $ a, вам придется использовать

 getRelativePath($b, $a); 

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

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

или:

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

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

 $filepath[0] == DIRECTORY_SEPARATOR 

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

 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"