2010-04-14 67 views
37

Tôi nhận thấy một số câu hỏi tương tự về vấn đề này khi tôi nhập tiêu đề, nhưng chúng dường như không có trong PHP. Vậy giải pháp cho nó với một hàm PHP là gì?Nhận đường dẫn tương đối từ đường dẫn tuyệt đối trong PHP

Để được chỉ định.

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

Bạn có ý tưởng hay không? Cảm ơn.

+9

Trường hợp sử dụng của bạn là gì khi cần một đường dẫn tương đối khi bạn có đường dẫn thực? –

+0

Bạn có thể vui lòng đăng những câu hỏi tương tự không? Viết cổng cho PHP là dễ dàng hơn mà reinviting tất cả mọi thứ. – Crozin

+2

@Tim Lytle, tôi nghĩ rằng nó có thể là một số ý nghĩa khi làm một số liên kết/bao gồm stuff.And cũng cho sự quan tâm. – Young

Trả lời

60

Hãy thử điều này một:

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

này sẽ cung cấp

$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

Dường như chúng tôi đã đưa ra gần như cùng một thuật toán chính xác, của tôi bằng tiếng Anh, của bạn trong PHP (tôi quá lười biếng quá mã nó, bạn không được). – webbiedave

+1

Tôi đã thử nghiệm tính năng này và tất cả các chức năng khác và bạn có thể tìm thấy điểm chuẩn của mình trong bài viết của tôi cuộn xuống. Việc triển khai này trở nên tuyệt vời nhất, vì vậy tôi khuyên bạn nên sử dụng tính năng này! ;-) – lucaferrario

8

Đường dẫn tương đối? Điều này có vẻ giống như một con đường du lịch. Bạn dường như muốn biết con đường một người đi để có được từ con đường A đến đường B. Nếu đó là trường hợp, bạn có thể explode $ a và $ b trên '/' sau đó inversely loop qua $ aParts, so sánh chúng với $ bParts của cùng chỉ mục cho đến khi thư mục "mẫu số chung" được tìm thấy (ghi lại số vòng lặp trên đường đi). Sau đó tạo một chuỗi rỗng và thêm '../' vào nó $ numLoops-1 lần rồi thêm vào đó $ b trừ thư mục mẫu số chung.

2

Dựa trên chức năng của Gordon, giải pháp của tôi là như sau:

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

Một số lý do Gordon không làm việc cho tôi .... Đây là giải pháp của tôi

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

Có thể bạn đặt đường dẫn thư mục bằng $ mà không có dấu gạch chéo. Những chức năng của Gordon và Young yêu cầu dấu gạch chéo cho các thư mục. Thật không may, chức năng của bạn hoạt động với một số đường dẫn nhưng không thành công với một số đường dẫn khác. Vui lòng đọc bài kiểm tra tôi đã thực hiện ở bài đăng khác. Chức năng này có vẻ không đáng tin cậy và không nên sử dụng. – lucaferrario

1

Tôi đã đến kết quả tương tự bằng cách sử dụng các thao tác mảng đó:

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

Đối số thứ hai là tệp mà đường dẫn có liên quan đến. Đó là tùy chọn để bạn có thể nhận được đường dẫn tương đối bất kể trang web hiện tại của bạn là gì. Để sử dụng nó với @Young hoặc @Gordon dụ, bởi vì bạn muốn biết đường dẫn tương đối so với $ b $ từ một, bạn sẽ phải sử dụng

getRelativePath($b, $a); 
+1

Thật không may, chức năng của bạn hoạt động với một số đường dẫn nhưng không thành công với một số đường dẫn khác. Vui lòng đọc bài kiểm tra tôi đã thực hiện ở bài đăng khác. Chức năng này có vẻ không đáng tin cậy và không nên sử dụng. Thay vào đó, tôi đề nghị sử dụng Gordon's, cái luôn trả về kết quả chính xác. – lucaferrario

+0

@lucaferrario, xin vui lòng kiểm tra [bình luận] của tôi (http://stackoverflow.com/questions/2637945/getting-relative-path-from-absolute-path-in-php/14329380#comment21288786_14878054) trên – loranger

13

Vì chúng ta đã có một số câu trả lời, Tôi quyết định kiểm tra tất cả chúng và chuẩn bị chúng. Tôi đã sử dụng đường dẫn này để kiểm tra:

$from = "/var/www/sites/web/mainroot/webapp/folder/sub/subf/subfo/subfol/subfold/lastfolder/"; LƯU Ý: nếu đó là thư mục, bạn phải đặt dấu gạch chéo để các chức năng hoạt động chính xác! Vì vậy, __DIR__ sẽ không hoạt động.Sử dụng __FILE__ thay hoặc __DIR__ . '/'

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

KẾT QUẢ: (phân cách thập phân là dấu phẩy, ngàn separator là dấu chấm)

  • Chức năng của Gordon: dẫn ĐÚNG, thời gian cho 100.000 giám đốc điều hành 1,222 giây
  • Chức năng của Trẻ: kết quả C ORRECT, thời gian cho 100.000 giám đốc điều hành 1.540 giây
  • Function bởi Ceagle: dẫn WRONG (nó hoạt động với một số đường dẫn nhưng không thành công với một số người khác, giống như những người sử dụng trong việc kiểm tra và viết ở trên)
  • Function bởi Loranger: kết quả WRONG (nó hoạt động với một số đường dẫn nhưng không thành công với một số người khác, giống như những người sử dụng trong việc kiểm tra và viết ở trên)

Vì vậy, tôi đề nghị bạn nên sử dụng thực hiện Gordon! (cái được đánh dấu là câu trả lời)

Trình duyệt của Young cũng tốt và hoạt động tốt hơn với cấu trúc thư mục đơn giản (như "a/b/c.php"), trong khi đó, công cụ của Gordon hoạt động tốt hơn với cấu trúc phức tạp. (giống như những cái được sử dụng trong tiêu chuẩn này).


Chú ý: Tôi viết ở đây bên dưới các kết quả trả với $from$to như đầu vào, vì vậy bạn có thể xác minh rằng 2 trong số đó là OK, trong khi 2 khác là sai:

  • Gordon: ../../../../../../aaa/bbb/ccc/ddd - -> ĐÚNG
  • Young: ../../../../../../aaa/bbb/ccc/ddd -> ĐÚNG
  • Ceagle: ../../../../../../bbb/ccc/ddd -> sAI
  • Loranger: ../../../../../aaa/bbb/ccc/ddd -> SAI
+0

đẹp chuẩn mực. Bạn đã kiểm tra đối với thư mục thay vì tệp (như được yêu cầu), đó là lý do tại sao chức năng của tôi không trả về đúng đường dẫn. Dù sao, bạn nói đúng, để đáng tin cậy chức năng này nên luôn luôn trả lại đường dẫn chính xác, bất kể nó sử dụng các tập tin hoặc thư mục, vì vậy tôi đã chỉ đơn giản là sửa chữa vấn đề này. Tôi sẽ tò mò để có được kết quả của tôi. Bạn có thể làm điểm chuẩn một lần khác không? – loranger

+1

Tôi xin lỗi Loranger, nhưng viết một kịch bản điểm chuẩn đáng tin cậy đã cho tôi một thời gian ... và tiếc là tôi không có nó nữa và tôi không có thời gian để viết một số khác để lặp lại bài kiểm tra. Dù sao, nếu bạn đã khắc phục vấn đề, hãy làm tốt!:-) – 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); 
} 

nỗ lực của tôi là cố tình đơn giản hơn, mặc dù có lẽ không khác nhau trong hoạt động. Tôi sẽ để lại điểm chuẩn như một bài tập cho người đọc tò mò. Tuy nhiên, điều này là khá mạnh mẽ và nên nền tảng bất khả tri.

Hãy coi chừng các giải pháp sử dụng các chức năng array_intersect nếu các thư mục song song có cùng tên. Ví dụ: getRelativePath('start/A/end/', 'start/B/end/') sẽ trả lại "../end" vì array_intersect tìm tất cả các tên bằng nhau, trong trường hợp này 2 khi chỉ nên có 1.

+0

Điều này gần giống với giải pháp mà tôi vừa đưa ra, ngoại trừ một chút sạch hơn với 'str_repeat' và' dirname'. Yêu nó! Cảm ơn. – mpen

2

Mã này được lấy từ máy phát điện 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

Simple one-liner cho các kịch bản phổ biến:

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

hay:

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

Để kiểm tra xem đường dẫn là tuyệt đối, hãy thử:

$filepath[0] == DIRECTORY_SEPARATOR 
0

Đây là những gì làm việc cho tôi. Đối với một số lý do nào đó, câu trả lời upvoted nhất cho câu hỏi này đã không làm việc như mong đợi

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

nếu $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"

Sau đó $c, đó là đường dẫn tương đối của $b từ $a, sẽ là

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

Các vấn đề liên quan