Я заметил некоторые подобные вопросы об этой проблеме, когда я набрал название, но они, похоже, не находятся в 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";
РЕЗУЛЬТАТЫ: (десятичный разделитель – запятая, разделитель тысяч – точка)
Поэтому я предлагаю вам использовать реализацию Гордона! (тот, который отмечен как ответ)
Янг тоже хорош и лучше работает с простыми структурами каталогов (например, «a / b / c.php»), в то время как Gordon's лучше работает со сложными структурами, с большим количеством подкаталогов (например, используемых в этом тесте).
ПРИМЕЧАНИЕ. Я пишу здесь ниже результатов, возвращаемых с помощью $from
и $to
качестве входных данных, поэтому вы можете проверить, что 2 из них в порядке, в то время как другие 2 неверны:
../../../../../../aaa/bbb/ccc/ddd
-> ПРАВИЛЬНО ../../../../../../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"