Question Obtenir un chemin relatif depuis le chemin absolu en PHP


J'ai remarqué des questions similaires sur ce problème lorsque j'ai tapé le titre, mais elles ne semblent pas être en PHP. Alors, quelle est la solution avec une fonction PHP?

À préciser.

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

De bonnes idées? Merci.


38
2018-04-14 13:58


origine


Réponses:


Essaye celui-là:

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

Cela donnera

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

et

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

et

$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

et

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

62
2018-04-14 14:39



Comme nous avons eu plusieurs réponses, j'ai décidé de les tester toutes et de les comparer. J'ai utilisé ces chemins pour tester:

$from = "/var/www/sites/web/mainroot/webapp/folder/sub/subf/subfo/subfol/subfold/lastfolder/"; NOTE: si c'est un dossier, vous devez mettre une barre oblique pour que les fonctions fonctionnent correctement! Alors, __DIR__ ne fonctionnera pas. Utilisation __FILE__ à la place ou __DIR__ . '/'

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

RÉSULTATS: (le séparateur décimal est une virgule, le séparateur de milliers est un point)

  • Fonction par Gordon: résultat CORRECT, le temps pour 100.000 execs 1,222 secondes
  • Fonction par Young: résultat CORRECT, le temps pour 100.000 execs 1 540 secondes
  • Fonction par Ceagle: résultat FAUX (il fonctionne avec certains chemins mais échoue avec d'autres, comme ceux utilisés dans les tests et écrits ci-dessus)
  • Fonction de Loranger: résultat FAUX (il fonctionne avec certains chemins mais échoue avec d'autres, comme ceux utilisés dans les tests et écrits ci-dessus)

Donc, je suggère que vous utilisiez l'implémentation de Gordon! (celui marqué comme réponse)

Celui de Young est également bon et fonctionne mieux avec des structures de répertoires simples (comme "a / b / c.php"), tandis que celui de Gordon est plus performant avec des structures complexes, avec de nombreux sous-répertoires (comme ceux utilisés dans ce benchmark).


NOTE: J'écris ci-dessous les résultats retournés avec $from et $to comme entrées, vous pouvez donc vérifier que 2 d’entre elles sont correctes, alors que 2 autres sont incorrectes:

  • Gordon: ../../../../../../aaa/bbb/ccc/ddd -> CORRECT
  • Jeune: ../../../../../../aaa/bbb/ccc/ddd -> CORRECT
  • Ceagle: ../../../../../../bbb/ccc/ddd -> FAUX
  • Loranger: ../../../../../aaa/bbb/ccc/ddd -> FAUX

14
2018-02-14 15:18



Chemin relatif? Cela ressemble plus à un parcours de voyage. Vous semblez vouloir connaître le chemin parcouru pour aller du chemin A au chemin B. Si tel est le cas, vous pouvez exploser $ a et $ b sur '/' puis inversement, parcourez $ aParts, en les comparant à $ bParts du même index jusqu'à ce que le répertoire "dénominateur commun" soit trouvé (en enregistrant le nombre de boucles en cours de route). Ensuite, créez une chaîne vide et ajoutez '../' à elle $ numLoops-1 fois puis ajoutez à ce $ b moins le répertoire du dénominateur commun.


8
2018-04-14 14:31



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

Ma tentative est délibérément plus simple, même si ses performances ne sont probablement pas différentes. Je vais laisser l'analyse comparative comme un exercice pour le lecteur curieux. Cependant, ceci est assez robuste et devrait être indépendant de la plate-forme.

Il faut se méfier les solutions utilisant array_intersect fonctions comme celles-ci vont casser si les répertoires parallèles ont le même nom. Par exemple getRelativePath('start/A/end/', 'start/B/end/') retournerais "../end" car array_intersect trouve tous les noms égaux, dans ce cas 2 quand il ne devrait y avoir que 1.


4
2017-12-02 23:34



Sur la base de la fonction de Gordon, ma solution est la suivante:

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
2018-04-15 02:04



Ce code provient du générateur d'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 (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
2018-01-02 06:45



Raison pour laquelle Gordon n'a pas fonctionné pour moi ... Voici ma solution

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
2017-09-28 08:16



Je suis arrivé au même résultat en utilisant ces manipulations de tableaux:

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

Le second argument est le fichier auquel le chemin est relatif. Il est facultatif afin que vous puissiez obtenir le chemin relatif, quelle que soit la page Web actuelle. Pour pouvoir l'utiliser avec l'exemple @Young ou @Gordon, parce que vous voulez connaître le chemin relatif vers $ b à partir de $ a, vous devrez utiliser

getRelativePath($b, $a);

1
2018-01-15 00:42



Simple one-liner pour les scénarios courants:

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

ou:

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

Pour vérifier si le chemin est absolu, essayez:

$filepath[0] == DIRECTORY_SEPARATOR

0
2018-03-11 19:23



Voici ce qui fonctionne pour moi. Pour une raison inconnue, la réponse la plus relevée à cette question n'a pas fonctionné comme prévu

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

si $a = "C:\xampp\htdocs\projects\SMS\App\www\App\index.php" et
   $b = "C:\xampp\htdocs\projects\SMS\App/www/App/bin/bootstrap/css/bootstrap.min.css"

alors $c, qui est le chemin relatif de $b de $a, sera

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


0
2017-11-24 05:56