web-dev-qa-db-fra.com

Obtenir le chemin relatif du chemin absolu dans 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?

À spécifier.

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

Des bonnes idées? Merci.

38
Young

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
Gordon

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

$from = "/var/www/sites/web/mainroot/webapp/folder/sub/subf/subfo/subfol/subfold/lastfolder/"; NOTE: s'il s'agit d'un dossier, vous devez insérer une barre oblique finale pour que les fonctions fonctionnent correctement! Donc, __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 une virgule, le séparateur de milliers est un point)

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

Alors, je vous suggère d'utiliser la mise en œuvre de Gordon! (celui marqué comme réponse)

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


NOTE: J'écris ci-dessous les résultats renvoyés avec $from et $to en tant qu'entrées afin que vous puissiez vérifier que 2 d'entre eux sont corrects, les 2 autres sont incorrects:

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

Chemin relatif? Cela ressemble plus à un chemin de voyage. Vous semblez vouloir connaître le chemin que vous empruntez pour aller du chemin A au chemin B. Si c'est le cas, vous pouvez exploser $ a et $ b sur '/' puis faire une boucle inversée entre $ aParts, en les comparant à bPièces 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). Créez ensuite une chaîne vide et ajoutez-lui '../' $ numLoops-1 fois, puis ajoutez-y $ b moins le répertoire du dénominateur commun.

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

Ma tentative est délibérément plus simple, bien que probablement pas différente en performance. Je laisserai le benchmarking comme exercice pour le lecteur curieux. Cependant, cela est assez robuste et devrait être agnostique de plate-forme.

Attention les solutions utilisant les fonctions array_intersect car celles-ci se briseront si les répertoires parallèles portent le même nom. Par exemple, getRelativePath('start/A/end/', 'start/B/end/') renverrait "../end" car array_intersect trouve tous les noms égaux, dans ce cas 2 où il ne devrait y en avoir qu'un.

4
clockworkgeek

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

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
ya.teck

Certaines raisons, les Gordon n'ont 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
Ceagle

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

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 deuxième argument est le fichier auquel le chemin est relatif. C'est facultatif pour que vous puissiez obtenir le chemin relatif quelle que soit la page Web que vous êtes actuellement . Pour pouvoir l'utiliser avec @Young ou @Gordon par exemple, car vous voulez connaître le chemin relatif vers $ b à partir de $ a, vous avoir à utiliser

getRelativePath($b, $a);
1
loranger

Une ligne simple 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
kenorb