web-dev-qa-db-ja.com

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'

良いアイデアはありますか?ありがとう。

40
Young

これを試してください:

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
64
Gordon

いくつかの回答があったので、すべてをテストしてベンチマークすることにしました。私はこのパスを使用してテストしました:

$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 execの時間1,222
  • 若者による機能:結果[〜#〜]正しい[〜#〜]、100.000 execの時間1,54
  • Ceagleによる関数:結果[〜#〜] wrong [〜#〜](テストで使用され、上記で記述されたパスのように、一部のパスでは機能しますが、他のパスでは失敗します)
  • Lorangerによる関数:結果[〜#〜] wrong [〜#〜](テストで使用された上記のように、一部のパスでは機能しますが、他のパスでは失敗します)

つまり、Gordonの実装を使用することをお勧めします!(回答としてマークされているもの)

ヤングのものも良いですし、単純なディレクトリ構造( "a/b/c.php"のような)でより良いパフォーマンスを発揮しますが、ゴードンのものは複雑な構造で多くのサブディレクトリ(このベンチマークで使用されるもの)でより良いパフォーマンスを発揮します。


注:ここでは、入力として$fromおよび$toを使用して返された結果を以下に示します。これにより、2つは問題ないが、他の2つは間違っていることを確認できます。

  • ゴードン:../../../../../../aaa/bbb/ccc/ddd->正解
  • 若い:../../../../../../aaa/bbb/ccc/ddd->正しい
  • Ceagle:../../../../../../bbb/ccc/ddd->誤り
  • ロランジャー:../../../../../aaa/bbb/ccc/ddd->誤り
16
lucaferrario

相対パス?これは旅路のようです。パスAからパスBに到達するために移動するパスを知りたいようです。その場合は、 explode $ aおよび$ b次に'/'は$ aPartsを逆にループし、「共通の分母」ディレクトリが見つかるまで(それらのループの数を記録するまで)同じインデックスの$ bPartsと比較します。次に、空の文字列を作成し、'../'を$ numLoops-1回追加して、その$ bから共通の分母ディレクトリを差し引いて追加します。

8
webbiedave
_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_がすべての等しい名前を検索するためです。

5
clockworkgeek

ゴードンの関数に基づいて、私の解決策は次のとおりです:

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;
}
2
Young

このコードはSymfony URLジェネレーターから取得されます 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 (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;
    }
2
ya.teck

いくつかの理由ゴードンがうまくいかなかった...ここに私の解決策があります

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

これらの配列操作を使用して同じ結果が得られました:

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

2番目の引数は、相対パスであるファイルです。これはオプションなので、現在のWebページに関係なく相対パスを取得できます。 @Youngまたは@Gordonの例で使用するには、$ aから$ bへの相対パスを知りたいので、

getRelativePath($b, $a);
1
loranger

一般的なシナリオのためのシンプルなワンライナー:

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

または:

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

パスが絶対パスかどうかを確認するには、次を試してください:

$filepath[0] == DIRECTORY_SEPARATOR
0
kenorb