I ran into this problem myself, first of all, regarding the normalization of paths.
Normalization:
- One delimiter (I decided to support but never return a backslash
\\ ) - Indirect Resolution:
/../ - Removing duplicate separators:
/home/www/uploads//file.ext - Always remove trailing delimiter.
I wrote a function that accomplishes this. I do not have access to this code right now, but it is also not so difficult to write it myself.
Regardless of whether the path is absolute or not, it does not really matter for the implementation of this normalization function, just keep an eye on the leading separator and you are good.
I'm not too worried about OS dependency. Both Windows and Linux PHP understand / , so for simplicity I always use this, but I think it doesn't matter which separator you use.
To answer your question: path concatenation can be very simple if you always use / and assume that there is no trailing separator in the directory. 'no trailing separator' seems like a good guess, because functions like dirname remove the trailing separator.
Then it is always safe: $dir . "/" . $file $dir . "/" . $file $dir . "/" . $file .
And even if the resulting path is /home/uploads/../uploads//my_uploads/myfile.ext , it will still work fine.
Normalization becomes useful when you need to save a path somewhere. And since you have this normalization function, you can make these assumptions.
An additional useful feature is a function for creating relative paths.
- / files / downloads
- /files/uploads/my_uploads/myfile.ext
It may be useful to extract from these two paths what is the relative path to the file.
Realpath
I found that the realpath extremely heavy. It's not so bad if you call it once, but if you do it in a loop somewhere, you get a pretty big hit. Keep in mind that every call to realpath is a call to the file system. Also, it will just return false , if you pass something stupid, I would prefer it to throw an exception.
For me, the realpath function is a good example of a BAD function, since it performs two functions: 1. It normalizes the path and 2. Checks if the path exists. Both of these functions are useful, of course, but they should be separated. It also does not distinguish between files and directories. For windows this is usually not a problem, but for Linux it can be.
And I think that when using realpath("") on Windows, there is some quirkiness. I think it will return \\ , which may be deeply unacceptable.
/** * This function is a proper replacement for realpath * It will _only_ normalize the path and resolve indirections (.. and .) * Normalization includes: * - directiory separator is always / * - there is never a trailing directory separator * @param $path * @return String */ function normalize_path($path) { $parts = preg_split(":[\\\/]:", $path); // split on known directory separators // resolve relative paths for ($i = 0; $i < count($parts); $i +=1) { if ($parts[$i] === "..") { // resolve .. if ($i === 0) { throw new Exception("Cannot resolve path, path seems invalid: `" . $path . "`"); } unset($parts[$i - 1]); unset($parts[$i]); $parts = array_values($parts); $i -= 2; } else if ($parts[$i] === ".") { // resolve . unset($parts[$i]); $parts = array_values($parts); $i -= 1; } if ($i > 0 && $parts[$i] === "") { // remove empty parts unset($parts[$i]); $parts = array_values($parts); } } return implode("/", $parts); } /** * Removes base path from longer path. The resulting path will never contain a leading directory separator * Base path must occur in longer path * Paths will be normalized * @throws Exception * @param $base_path * @param $longer_path * @return string normalized relative path */ function make_relative_path($base_path, $longer_path) { $base_path = normalize_path($base_path); $longer_path = normalize_path($longer_path); if (0 !== strpos($longer_path, $base_path)) { throw new Exception("Can not make relative path, base path does not occur at 0 in longer path: `" . $base_path . "`, `" . $longer_path . "`"); } return substr($longer_path, strlen($base_path) + 1); }