49 votes

Obtenir un chemin relatif à partir d'un 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 à ce problème 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.

73voto

Gordon Points 156415

Essaie celle-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

y

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

y

$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

y

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

20voto

lucaferrario Points 425

Comme nous avons eu plusieurs réponses, j'ai décidé de toutes les tester 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/"; REMARQUE : s'il s'agit d'un dossier, vous devez mettre un slash de fin pour que les fonctions fonctionnent correctement ! Ainsi, __DIR__ ne fonctionnera pas. Utilisez __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 la virgule, le séparateur des milliers est le point)

  • Fonction de Gordon : résultat CORRECT le temps pour 100.000 exécutifs 1,222 secondes
  • Fonction par Young : résultat CORRECT le temps pour 100.000 exécutifs 1,540 secondes
  • Fonction de Ceagle : résultat MAUVAIS (cela 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 MAUVAIS (cela fonctionne avec certains chemins mais échoue avec d'autres, comme ceux utilisés dans les tests et écrits ci-dessus)

Je vous suggère donc d'utiliser la mise en œuvre de Gordon ! (celle qui est marquée comme réponse)

Celle de Young est également bonne et donne de meilleurs résultats avec des structures de répertoire simples (comme "a/b/c.php"), tandis que celle de Gordon donne de meilleurs résultats avec des structures complexes, avec beaucoup de sous-répertoires (comme ceux utilisés dans ce benchmark).


NOTE : J'écris ci-dessous les résultats retournés avec $from y $to comme entrées, afin que vous puissiez vérifier que 2 d'entre elles sont correctes, tandis que les 2 autres sont fausses :

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

9voto

webbiedave Points 28781

Chemin relatif ? Cela ressemble plus à un chemin de voyage. Vous semblez vouloir connaître le chemin que l'on parcourt pour aller du chemin A au chemin B. Si c'est le cas, vous pouvez exploser $a et $b sur '/' puis boucle inversement sur les $aParts, en les comparant aux $bParts du même indice 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 '../' à celui-ci $numLoops-1 fois puis ajoutez à cela $b moins le répertoire du dénominateur commun.

6voto

clockworkgeek Points 25923
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, bien que les performances ne soient probablement pas différentes. Je laisse l'analyse comparative comme un exercice pour le lecteur curieux. Cependant, c'est assez robuste et devrait être indépendant de la plate-forme.

Méfiez-vous de les solutions en utilisant array_intersect car elles seront interrompues si des répertoires parallèles portent le même nom. Par exemple getRelativePath('start/A/end/', 'start/B/end/') retournerait " ../end " car array_intersect trouve tous les noms égaux, dans ce cas 2 alors qu'il ne devrait y en avoir qu'un.

2voto

Young Points 3715

En me basant sur 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;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X