41

This should be a simple question, but I just can't recall the relevant API. A search on google with the term "combine directory name php" doesn't yield any results . So I guess I am doing both myself and the programming community a service by asking this question. this is now the top entry returned by Google and DDG!

How to combine directory and file name to form a full file path in PHP? Let's say the directory name is "D:\setup program", and the file name is "mj.txt". The method should return me, on Windows "D:\setup program\mj.txt". Of course the method should return the correct file path in Linux or other OS.

The related function in .Net is Path.Combine, but in PHP, I couldn't recall that, even though I must have seen it before.

Graviton
  • 81,782
  • 146
  • 424
  • 602

7 Answers7

48
$filepath = $path . DIRECTORY_SEPARATOR . $file;

Although in newer versions of PHP it doesn't matter which way the slashes go, so it is fine to always use forward slashes.

You can get a correct absolute path using realpath(), this will also remove things like extra unnecessary slashes and resolve references like ../. It will return false if the path is not valid.

Tom Haigh
  • 57,217
  • 21
  • 114
  • 142
  • 1
    I don't think that PHP accepting slashes on windows has anything to do with PHP. You can enter forward slashes in newer Windows versions in the shell/explorer. – soulmerge Jun 26 '09 at 16:20
  • 3
    Beware that '' . DIRECTORY_SEPARATOR . 'foo.txt' will convert a relative path to an absolute path. – EricP Jan 03 '11 at 03:17
  • 1
    What if $file is already an absolute path? Path.Combine deals with this, but simple string concatenation won't. – yoyo Mar 20 '12 at 02:18
  • 1
    Note that realpath() returns false even if the file pointed to by the path does not exist! So you should not use realpath() if you about to create the file for $filepath. –  Jun 12 '14 at 20:51
  • 2
    This is not the equivalent of .Net's Path.Combine, since "C:\" and "file.php" will be incorrectly combined as "C:\\file.php" – TZubiri Aug 15 '17 at 13:22
  • @TomasZubiri maybe it's not equivalent, but there's no real issue with multiple backslashes in Windows paths: https://stackoverflow.com/questions/33027024/documented-behavior-for-multiple-backslashes-in-windows-paths – Ohad Schneider Aug 20 '17 at 13:24
  • "Although in newer versions of PHP it doesn't matter which way the slashes go" - starting which version? – Ohad Schneider Aug 20 '17 at 13:26
15

I think the most clean and flexible way to do it would be using the join function plus the DIRECTORY_SEPARATOR constant:

$fullPath = join(DIRECTORY_SEPARATOR, array($directoryPath, $fileName));
slashCoder
  • 1,447
  • 21
  • 22
6

All given answers don't encouter empty values in the $directoryPath and don't handle duplicates slashes. While it is true that PHP is very error tolerant the first point can be fatal and the second point shouldn't be ignored if you're writing clean code.

So the correct solution is:

function PathCombine($one, $other, $normalize = true) {

    # normalize
    if($normalize) {
        $one = str_replace('/', DIRECTORY_SEPARATOR, $one);
        $one = str_replace('\\', DIRECTORY_SEPARATOR, $one);
        $other = str_replace('/', DIRECTORY_SEPARATOR, $other);
        $other = str_replace('\\', DIRECTORY_SEPARATOR, $other);
    }

    # remove leading/trailing dir separators
    if(!empty($one) && substr($one, -1)==DIRECTORY_SEPARATOR) $one = substr($one, 0, -1);
    if(!empty($other) && substr($other, 0, 1)==DIRECTORY_SEPARATOR) $other = substr($other, 1);

    # return combined path
    if(empty($one)) {
        return $other;
    } elseif(empty($other)) {
        return $one;
    } else {
        return $one.DIRECTORY_SEPARATOR.$other;
    }

}

Only limitation is that the second parameter must not be an absolute path.

jor
  • 2,058
  • 2
  • 26
  • 46
5

10 years later, but maybe this will help the next ones. Here's what I've done to make it compatible with PHP 7.4+.

It works just like Path.Combine except that the \ or / at the beginning of the string will not exclude the previous arguments.

class Path
{
    public static function combine (): string
    {
        $paths = func_get_args();
        $paths = array_map(fn($path) => str_replace(["\\", "/"], DIRECTORY_SEPARATOR, $path), $paths);
        $paths = array_map(fn($path) => self::trimPath($path), $paths);
        return implode(DIRECTORY_SEPARATOR, $paths);
    }

    private static function trimPath(string $path): string
    {
        $path = trim($path);
        $start = $path[0] === DIRECTORY_SEPARATOR ? 1 : 0;
        $end = $path[strlen($path) - 1] === DIRECTORY_SEPARATOR ? -1 : strlen($path);
        return substr($path, $start, $end);
    }
}

Path::combine("C:\Program Files", "/Repository", "sub-repository/folder/", "file.txt");
//return "C:\Program Files\Repository\sub-repository\folder\file.txt"

Path::combine("C:\Program Files", "/Repository/", "\\sub-repository\\folder\\", "sub-folder", "file.txt");
//return "C:\Program Files\Repository\sub-repository\folder\sub-folder\file.txt"

Path::combine("C:\file.txt");
//return "C:\file.txt"

Path::combine();
//return ""
0

You can just concatenate it with the php constant DIRECTORY_SEPARATOR, or just use forward slashes. Windows probably won't mind =D

Ry-
  • 218,210
  • 55
  • 464
  • 476
Zenshai
  • 10,307
  • 2
  • 19
  • 18
  • 3
    That might not work if the $folderPath ends with '/' - so the path generated would be "/var/somepath//fileName". – Dai Nov 18 '11 at 00:03
  • Actually `//` works fine most of the time. At least on Windows and in bash. Not sure were it would cause problems. – Nux Nov 05 '19 at 16:27
0

This is not exactly what you were looking for but it should get an array of path parts, then join the parts using DIRECTORY_SEPARATOR then split the joined parts using DIRECTORY_SEPARATOR and remove the empty path parts. It should return the remaining path parts joined by DIRECTORY_SEPARATOR.

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

Example usage:

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

Will output:

/cosecheamo/pizze/4formaggi/GORGONZOLA
user6307854
  • 99
  • 1
  • 4
0

Try this function. I use this function to meet my own needs.

If you want to check the path, you must set $isReal to true

  public static function path($base, $com = null, $isReal = false)
  {
    if(substr($base, -1)!=DIRECTORY_SEPARATOR) $base.=DIRECTORY_SEPARATOR;
    if($com) $base.=$com;
    $base = preg_replace('/(\/+|\\\\+)/', DIRECTORY_SEPARATOR, $base);
    while(preg_match('/(\/[\w\s_-]+\/\.\.)/', $base)){
      $base = preg_replace('/(\/[\w\s_-]+\/\.\.)/', "", $base);
      if(preg_match('/\/\.\.\//', $base))
        throw new \Exception("Error directory don't have parent folder!", 1);
    }
    if($isReal){
      $base = realpath($base);
      if(is_dir($base)) $base .= DIRECTORY_SEPARATOR;
    }
    return $base;
  }

Example output:

var_dump(Combine::path("www///system", "Combine/../"));
// string(11) "www/system/"

var_dump(Combine::path("System", "Combine/../", true));
// string(40) "/home/snow/Desktop/localhost/www/System/"

var_dump(Combine::path("System", "Combine", true));
// string(48) "/home/snow/Desktop/localhost/www/System/Combine/"

var_dump(Combine::path("System", "Combine/notPath", true)); // if you try to create a path that does not exist
// bool(false)

var_dump(Combine::path("System", "Combine/class.Combine.php", true)); // you can also select a file
//string(65) "/home/snow/Desktop/localhost/www/System/Combine/class.Combine.php"

var_dump(Combine::path("/home/testuser\\badPath///////repair"));
// string(30) "/home/testuser/badPath/repair/"