web-dev-qa-db-fra.com

Comment joindre des chaînes de chemin de système de fichiers en PHP?

Existe-t-il une fonction intégrée dans PHP pour joindre intelligemment les chaînes de chemin? La fonction, avec pour arguments "abc/de /" et "/fg/x.php", devrait renvoyer "abc/de/fg/x.php"; le même résultat devrait être donné en utilisant "abc/de" et "fg/x.php" comme arguments de cette fonction.

Si non, y a-t-il une classe disponible? Cela pourrait également être utile pour scinder des chemins ou en supprimer des parties. Si vous avez écrit quelque chose, pouvez-vous partager votre code ici?

Il est correct de toujours utiliser "/", je ne code que pour Linux.

En Python, il y a os.path.join(), ce qui est excellent.

62
user89021

Comme cela semble être une question populaire et que les commentaires se remplissent de "suggestions de fonctionnalités" ou de "rapports de bogues" ... Tout cet extrait de code ne fait que joindre deux chaînes avec une barre oblique sans les dupliquer. C'est tout. Ni plus ni moins. Il n'évalue pas les chemins réels sur le disque dur et ne conserve pas non plus la barre oblique de début (ajoutez-le si nécessaire, au moins, vous pouvez être sûr que ce code renvoie toujours une chaîne sans barre oblique de départ).

join('/', array(trim("abc/de/", '/'), trim("/fg/x.php", '/')));

Le résultat final sera toujours un chemin sans barres obliques au début ou à la fin et sans doubles barres obliques. N'hésitez pas à en faire une fonction.

EDIT: Voici un wrapper de fonctions flexible et agréable pour l'extrait ci-dessus. Vous pouvez transmettre autant d'extraits de chemin que vous le souhaitez, sous forme de tableau ou d'arguments séparés:

function joinPaths() {
    $args = func_get_args();
    $paths = array();
    foreach ($args as $arg) {
        $paths = array_merge($paths, (array)$arg);
    }

    $paths = array_map(create_function('$p', 'return trim($p, "/");'), $paths);
    $paths = array_filter($paths);
    return join('/', $paths);
}

echo joinPaths(array('my/path', 'is', '/an/array'));
//or
echo joinPaths('my/paths/', '/are/', 'a/r/g/u/m/e/n/t/s/');

: o)

46
deceze
function join_paths() {
    $paths = array();

    foreach (func_get_args() as $arg) {
        if ($arg !== '') { $paths[] = $arg; }
    }

    return preg_replace('#/+#','/',join('/', $paths));
}

Ma solution est plus simple et plus semblable à la façon dont Python os.path.join fonctionne

Considérons ces cas de test

array               my version    @deceze      @david_miller    @mark

['','']             ''            ''           '/'              '/'
['','/']            '/'           ''           '/'              '/'
['/','a']           '/a'          'a'          '//a'            '/a'
['/','/a']          '/a'          'a'          '//a'            '//a'
['abc','def']       'abc/def'     'abc/def'    'abc/def'        'abc/def'
['abc','/def']      'abc/def'     'abc/def'    'abc/def'        'abc//def'
['/abc','def']      '/abc/def'    'abc/def'    '/abc/def'       '/abc/def'
['','foo.jpg']      'foo.jpg'     'foo.jpg'    '/foo.jpg'       '/foo.jpg'
['dir','0','a.jpg'] 'dir/0/a.jpg' 'dir/a.jpg'  'dir/0/a.jpg'    'dir/0/a.txt'
106
Riccardo Galli

La fonction de @ deceze ne garde pas la position de tête/lorsque vous essayez de joindre un chemin qui commence par un chemin absolu Unix, par exemple. joinPaths('/var/www', '/vhosts/site');.

function unix_path() {
  $args = func_get_args();
  $paths = array();

  foreach($args as $arg) {
    $paths = array_merge($paths, (array)$arg);
  }

  foreach($paths as &$path) {
    $path = trim($path, '/');
  }

  if (substr($args[0], 0, 1) == '/') {
    $paths[0] = '/' . $paths[0];
  }

  return join('/', $paths);
}
17
David Miller

Ma prise

function trimds($s) {
    return rtrim($s,DIRECTORY_SEPARATOR);
}

function joinpaths() {
    return implode(DIRECTORY_SEPARATOR, array_map('trimds', func_get_args()));
}

J'aurais utilisé une fonction anonyme pour trimds, mais les anciennes versions de PHP ne la prennent pas en charge.

Exemple:

join_paths('a','\\b','/c','d/','/e/','f.jpg'); // a\b\c\d\e\f.jpg (on Windows)

Mis à jour Avril 2013Mars 2014 Mai 2018 :

function join_paths(...$paths) {
    return preg_replace('~[/\\\\]+~', DIRECTORY_SEPARATOR, implode(DIRECTORY_SEPARATOR, $paths));
}

Celui-ci corrigera toutes les barres obliques pour qu'elles correspondent à votre système d'exploitation, ne supprimera pas les barres obliques majeures, nettoiera et supprimera plusieurs barres obliques de suite.

14
mpen

Si vous savez que le fichier/répertoire existe , vous pouvez ajouter des barres obliques (qui peuvent être inutiles), puis appelez realpath , c'est à dire.

realpath(join('/', $parts));

Ce n'est bien sûr pas tout à fait la même chose que la version Python, mais dans de nombreux cas, cela peut suffire.

6
George Lund

Une alternative consiste à utiliser implode() et explode().

$a = '/a/bc/def/';
$b = '/q/rs/tuv/path.xml';

$path = implode('/',array_filter(explode('/', $a . $b)));

echo $path;  // -> a/bc/def/q/rs/tuv/path.xml
4
Chris J

vous pouvez utiliser pathinfo http://nz2.php.net/manual/en/function.pathinfo.php

pour rejoindre la réponse de @deceze semble bien

2
bumperbox

Une autre façon d’attaquer celui-ci:

function joinPaths() {
  $paths = array_filter(func_get_args());
  return preg_replace('#/{2,}#', '/', implode('/', $paths));
}
2
stompydan

Meilleure solution trouvée:

function joinPaths($leftHandSide, $rightHandSide) { 
    return rtrim($leftHandSide, '/') .'/'. ltrim($rightHandSide, '/'); 
}

Remarque: copié à partir du commentaire par user89021

1
Basil Musa

La solution ci-dessous utilise la logique proposée par @RiccardoGalli, mais est améliorée pour pouvoir utiliser la constante DIRECTORY_SEPARATOR, comme suggéré par @Qix et @ FélixSaparelli et, plus important encore, pour couper chaque élément donné pour éviter l'espace. seuls les noms de dossiers apparaissant dans le chemin final (c’était une exigence dans mon cas).

En ce qui concerne l’échappement du séparateur de répertoire à l’intérieur du modèle preg_replace(), comme vous pouvez le constater, j’ai utilisé la fonction preg_quote() qui permet d’effectuer le travail correctement.
De plus, je ne remplacerais que plusieurs séparateurs uniquement (RegExp quantifier {2,}).

// PHP 7.+
function paths_join(string ...$parts): string {
    $parts = array_map('trim', $parts);
    $path = [];

    foreach ($parts as $part) {
        if ($part !== '') {
            $path[] = $part;
        }
    }

    $path = implode(DIRECTORY_SEPARATOR, $path);

    return preg_replace(
        '#' . preg_quote(DIRECTORY_SEPARATOR) . '{2,}#',
        DIRECTORY_SEPARATOR,
        $path
    );
}
1

Cela semble être un travail plutôt bien et me semble assez soigné.

private function JoinPaths() {
  $slash = DIRECTORY_SEPARATOR;
  $sections = preg_split(
          "@[/\\\\]@",
          implode('/', func_get_args()),
          null,
          PREG_SPLIT_NO_EMPTY);
  return implode($slash, $sections);
}
1
Kenny Hung

Ceci est une version corrigée de la fonction postée par deceze. Sans ce changement, joinPaths ('', 'foo.jpg') devient '/foo.jpg'

function joinPaths() {
    $args = func_get_args();
    $paths = array();
    foreach ($args as $arg)
        $paths = array_merge($paths, (array)$arg);

    $paths2 = array();
    foreach ($paths as $i=>$path)
    {   $path = trim($path, '/');
        if (strlen($path))
            $paths2[]= $path;
    }
    $result = join('/', $paths2); // If first element of old path was absolute, make this one absolute also
    if (strlen($paths[0]) && substr($paths[0], 0, 1) == '/')
        return '/'.$result;
    return $result;
}
1
Dwayne

Voici une fonction qui se comporte comme Le path.resolve :

function resolve_path() {
    $working_dir = getcwd();
    foreach(func_get_args() as $p) {
        if($p === null || $p === '') continue;
        elseif($p[0] === '/') $working_dir = $p;
        else $working_dir .= "/$p";
    }
    $working_dir = preg_replace('~/{2,}~','/', $working_dir);
    if($working_dir === '/') return '/';
    $out = [];
    foreach(explode('/',rtrim($working_dir,'/')) as $p) {
        if($p === '.') continue;
        if($p === '..') array_pop($out);
        else $out[] = $p;
    }
    return implode('/',$out);
}

Cas de test:

resolve_path('/foo/bar','./baz')         # /foo/bar/baz
resolve_path('/foo/bar','/tmp/file/')    # /tmp/file
resolve_path('/foo/bar','/tmp','file')   # /tmp/file
resolve_path('/foo//bar/../baz')         # /foo/baz
resolve_path('/','foo')                  # /foo
resolve_path('/','foo','/')              # /
resolve_path('wwwroot', 'static_files/png/', '../gif/image.gif') 
                                  # __DIR__.'/wwwroot/static_files/gif/image.gif'
0
mpen

De la grande réponse de Ricardo Galli, un peu d'amélioration pour éviter de tuer le préfixe de protocole.

L'idée est de tester la présence d'un protocole dans un argument et de le conserver dans le résultat. ATTENTION: ceci est une implémentation naïve!

Par exemple:

array("http://domain.de","/a","/b/")

résultats à (protocole de maintien)

"http://domain.de/a/b/"

au lieu de (protocole de mise à mort)

"http:/domain.de/a/b/"

Mais http://codepad.org/hzpWmpzk a besoin de meilleures compétences en écriture de code.

0
nicolallias
function path_combine($paths) {
  for ($i = 0; $i < count($paths); ++$i) {
    $paths[$i] = trim($paths[$i]);
  }

  $dirty_paths = explode(DIRECTORY_SEPARATOR, join(DIRECTORY_SEPARATOR, $paths));
  for ($i = 0; $i < count($dirty_paths); ++$i) {
    $dirty_paths[$i] = trim($dirty_paths[$i]);
  }

  $unslashed_paths = array();

  for ($i = 0; $i < count($dirty_paths); ++$i) {
    $path = $dirty_paths[$i];
    if (strlen($path) == 0) continue;
    array_Push($unslashed_paths, $path);
  }

  $first_not_empty_index = 0;
  while(strlen($paths[$first_not_empty_index]) == 0) {
    ++$first_not_empty_index;
  }
  $starts_with_slash = $paths[$first_not_empty_index][0] == DIRECTORY_SEPARATOR;

  return $starts_with_slash
    ? DIRECTORY_SEPARATOR . join(DIRECTORY_SEPARATOR, $unslashed_paths)
    : join(DIRECTORY_SEPARATOR, $unslashed_paths);
}

Exemple d'utilisation:

$test = path_combine([' ', '/cosecheamo', 'pizze', '///// 4formaggi', 'GORGONZOLA']);
echo $test;

Est-ce que la sortie:

/cosecheamo/pizze/4formaggi/GORGONZOLA
0
user6307854

Une manière unique de joindre un chemin PHP inspiré de Python.

Ce code n'utilise pas de tableau inutile.

Multi plateforme

function os_path_join(...$parts) {
  return preg_replace('#'.DIRECTORY_SEPARATOR.'+#', DIRECTORY_SEPARATOR, implode(DIRECTORY_SEPARATOR, array_filter($parts)));
}

Systèmes basés sur Unix

function os_path_join(...$parts) {
  return preg_replace('#/+#', '/', implode('/', array_filter($parts)));
}

Système basé sur Unix sans paramètres REST (ne respectez pas la philosophie explicite de PEP8):

function os_path_join() {
  return preg_replace('#/+#', '/', implode('/', array_filter(func_get_args())));
}

Usage

$path = os_path_join("", "/", "mydir/", "/here/");

Bonus: si vous voulez vraiment suivre Python os.path.join (). Le premier argument est requis:

function os_path_join($path=null, ...$paths) {
  if (!is_null($path)) {
    throw new Exception("TypeError: join() missing 1 required positional argument: 'path'", 1);
  }
  $path = rtrim($path, DIRECTORY_SEPARATOR);
  foreach ($paths as $key => $current_path) {
    $paths[$key] = $paths[$key] = trim($current_path, DIRECTORY_SEPARATOR);
  }
  return implode(DIRECTORY_SEPARATOR, array_merge([$path], array_filter($paths)));
}

Vérifiez la source os.path.join () si vous voulez: https://github.com/python/cpython/blob/master/Lib/ntpath.py

0
jedema

J'aime la réponse de Riccardo et je pense que c'est la meilleure réponse.

Je l'utilise pour joindre des chemins dans le bâtiment url, mais avec un petit changement pour gérer la double barre oblique des protocoles:

function joinPath () {
    $paths = array();

    foreach (func_get_args() as $arg) {
        if ($arg !== '') { $paths[] = $arg; }
    }

    // Replace the slash with DIRECTORY_SEPARATOR
    $paths = preg_replace('#/+#', '/', join('/', $paths));
    return preg_replace('#:/#', '://', $paths);
}
0
Smily

Version indépendante de l'OS basée sur la réponse par mpen mais encapsulée dans une seule fonction et avec l'option d'ajouter un séparateur de chemin de fin.

function joinPathParts($parts, $trailingSeparator = false){
    return implode(
        DIRECTORY_SEPARATOR, 
        array_map(
            function($s){
                return rtrim($s,DIRECTORY_SEPARATOR);
            }, 
            $parts)
        )
        .($trailingSeparator ? DIRECTORY_SEPARATOR : '');
}

Ou pour vous les amateurs de one-liner:

function joinPathParts($parts, $trailingSeparator = false){
    return implode(DIRECTORY_SEPARATOR, array_map(function($s){return rtrim($s,DIRECTORY_SEPARATOR);}, $parts)).($trailingSeparator ? DIRECTORY_SEPARATOR : '');
}

Appelez-le simplement avec un tableau de chemins:

// No trailing separator - ex. C:\www\logs\myscript.txt
$logFile = joinPathParts([getcwd(), 'logs', 'myscript.txt']);

// Trailing separator - ex. C:\www\download\images\user1234\
$dir = joinPathParts([getcwd(), 'download', 'images', 'user1234'], true);
0
Magnus W