Apparently, realpath is very buggy. In PHP 5.3.1, it causes random crashes.
In 5.3.0 and less, realpath randomly fails and returns false (for the same string of course), plus it always fails on realpath-ing the same string twice/more (and of course, it works the first time).
Also, it is so buggy in earlier PHP versions, that it is completely unusable. Well...it already is, since it's not consistent.
Anyhow, what options do I have? Maybe rewrite it by myself? Is this advisable?
Thanks to Sven Arduwie's code (pointed out by Pekka) and some modification, I've built a (hopefully) better implementation:
/**
* This function is to replace PHP's extremely buggy realpath().
* #param string The original path, can be relative etc.
* #return string The resolved path, it might not exist.
*/
function truepath($path){
// whether $path is unix or not
$unipath=strlen($path)==0 || $path{0}!='/';
// attempts to detect if path is relative in which case, add cwd
if(strpos($path,':')===false && $unipath)
$path=getcwd().DIRECTORY_SEPARATOR.$path;
// resolve path parts (single dot, double dot and double delimiters)
$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
$absolutes = array();
foreach ($parts as $part) {
if ('.' == $part) continue;
if ('..' == $part) {
array_pop($absolutes);
} else {
$absolutes[] = $part;
}
}
$path=implode(DIRECTORY_SEPARATOR, $absolutes);
// resolve any symlinks
if(file_exists($path) && linkinfo($path)>0)$path=readlink($path);
// put initial separator that could have been lost
$path=!$unipath ? '/'.$path : $path;
return $path;
}
NB: Unlike PHP's realpath, this function does not return false on error; it returns a path which is as far as it could to resolving these quirks.
Note 2: Apparently some people can't read properly. Truepath() does not work on network resources including UNC and URLs. It works for the local file system only.
here is the modified code that supports UNC paths as well
static public function truepath($path)
{
// whether $path is unix or not
$unipath = strlen($path)==0 || $path{0}!='/';
$unc = substr($path,0,2)=='\\\\'?true:false;
// attempts to detect if path is relative in which case, add cwd
if(strpos($path,':') === false && $unipath && !$unc){
$path=getcwd().DIRECTORY_SEPARATOR.$path;
if($path{0}=='/'){
$unipath = false;
}
}
// resolve path parts (single dot, double dot and double delimiters)
$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
$absolutes = array();
foreach ($parts as $part) {
if ('.' == $part){
continue;
}
if ('..' == $part) {
array_pop($absolutes);
} else {
$absolutes[] = $part;
}
}
$path = implode(DIRECTORY_SEPARATOR, $absolutes);
// resolve any symlinks
if( function_exists('readlink') && file_exists($path) && linkinfo($path)>0 ){
$path = readlink($path);
}
// put initial separator that could have been lost
$path = !$unipath ? '/'.$path : $path;
$path = $unc ? '\\\\'.$path : $path;
return $path;
}
I know this is an old thread, but it is really helpful.
I meet a weird Phar::interceptFileFuncs issue when I implemented relative path in phpctags, the realpath() is really really buggy inside phar.
Thanks this thread give me some lights, here comes with my implementation based on christian's implemenation from this thread and this comments.
Hope it works for you.
function relativePath($from, $to)
{
$fromPath = absolutePath($from);
$toPath = absolutePath($to);
$fromPathParts = explode(DIRECTORY_SEPARATOR, rtrim($fromPath, DIRECTORY_SEPARATOR));
$toPathParts = explode(DIRECTORY_SEPARATOR, rtrim($toPath, DIRECTORY_SEPARATOR));
while(count($fromPathParts) && count($toPathParts) && ($fromPathParts[0] == $toPathParts[0]))
{
array_shift($fromPathParts);
array_shift($toPathParts);
}
return str_pad("", count($fromPathParts)*3, '..'.DIRECTORY_SEPARATOR).implode(DIRECTORY_SEPARATOR, $toPathParts);
}
function absolutePath($path)
{
$isEmptyPath = (strlen($path) == 0);
$isRelativePath = ($path{0} != '/');
$isWindowsPath = !(strpos($path, ':') === false);
if (($isEmptyPath || $isRelativePath) && !$isWindowsPath)
$path= getcwd().DIRECTORY_SEPARATOR.$path;
// resolve path parts (single dot, double dot and double delimiters)
$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
$pathParts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
$absolutePathParts = array();
foreach ($pathParts as $part) {
if ($part == '.')
continue;
if ($part == '..') {
array_pop($absolutePathParts);
} else {
$absolutePathParts[] = $part;
}
}
$path = implode(DIRECTORY_SEPARATOR, $absolutePathParts);
// resolve any symlinks
if (file_exists($path) && linkinfo($path)>0)
$path = readlink($path);
// put initial separator that could have been lost
$path= (!$isWindowsPath ? '/'.$path : $path);
return $path;
}
For those Zend users out there, THIS answer may help you, as it did me:
$path = APPLICATION_PATH . "/../directory";
$realpath = new Zend_Filter_RealPath(new Zend_Config(array('exists' => false)));
$realpath = $realpath->filter($path);
I have never heard of such massive problems with realpath() (I always thought that it just interfaces some underlying OS functionality - would be interested in some links), but the User Contributed Notes to the manual page have a number of alternative implementations. Here is one that looks okay.
Of course, it's not guaranteed these implementations take care of all cross-platform quirks and issues, so you'd have to do thorough testing to see whether it suits your needs.
As far as I can see though, none of them returns a canonicalized path, they only resolve relative paths. If you need that, I'm not sure whether you can get around realpath() (except perhaps executing a (system-dependent) console command that gives you the full path.)
On Windows 7, the code works fine. On Linux, there is a problem in that the path generated starts with (in my case) home/xxx when it should start with /home/xxx ... ie the initial /, indicating the root folder, is missing.
The problem is not so much with this function, but with what getcwd returns in Linux.
Related
I am having php admin panel and pure-ftpd server running in the background.
I want to check if the path that user has provided for specific user DOES NOT contain ../ /.. or /../
I am using a php framework Nette and for this validation I am using addRule(Form::PATTERN, 'error message', $pattern) (Nette api-docs: https://api.nette.org/2.4/Nette.Forms.Form.html, Nette docs: https://doc.nette.org/en/2.4/form-validation
Thank you for your time
I don't know if you're open to other solutions, but if it were me I would not attempt to use regex on the path because PHP has an easier way.
If it were me, I would do this:
<?php
// You know your users are limited to their own home directory
$user_base_dir = '/home/skunkbad/';
// And they supply some path that attempts directory transversal
$supplied_path = '/home/skunkbad/../../etc/';
// Realpath in this case would return "/etc/"
$realpath = realpath( $supplied_path );
// So the check to see if the user base directory is in the path that realpath returns would fail
if( stripos( $realpath, $user_base_dir ) !== 0 )
echo 'Invalid Path!';
See: http://php.net/manual/en/function.realpath.php
and: http://php.net/manual/en/function.stripos.php
Thank you #BrianGottier,
but realpath($path) checks if the file really exists in the filesystem and script need permission to execute... So, since I knew what I was looking for I googled and with some stuff from php.net & community i have came up with this:
/**
* #param $path
* #param $baseDir
* #return bool
*/
protected function isDirValid($path, $baseDir)
{
if (stripos($path, '\\'))
return false;
$path = str_replace('/', '/', $path);
$path = str_replace('\\', '/', $path);
$parts = array_filter(explode('/', $path), 'strlen');
$absolutes = [];
foreach ($parts as $part)
{
if ('.' == $part)
continue;
if ('..' == $part)
{
array_pop($absolutes);
} else {
$absolutes[] = $part;
}
}
$realDir = '/' . implode('/', $absolutes);
if (stripos($realDir, $baseDir) !== 0)
return false;
return true;
}
I need to extract the name of the direct sub directory from a full path string.
For example, say we have:
$str = "dir1/dir2/dir3/dir4/filename.ext";
$dir = "dir1/dir2";
Then the name of the sub-directory in the $str path relative to $dir would be "dir3". Note that $dir never has '/' at the ends.
So the function should be:
$subdir = getsubdir($str,$dir);
echo $subdir; // Outputs "dir3"
If $dir="dir1" then the output would be "dir2". If $dir="dir1/dir2/dir3/dir4" then the output would be "" (empty). If $dir="" then the output would be "dir1". Etc..
Currently this is what I have, and it works (as far as I've tested it). I'm just wondering if there's a simpler way since I find I'm using a lot of string functions. Maybe there's some magic regexp to do this in one line? (I'm not too good with regexp unfortunately).
function getsubdir($str,$dir) {
// Remove the filename
$str = dirname($str);
// Remove the $dir
if(!empty($dir)){
$str = str_replace($dir,"",$str);
}
// Remove the leading '/' if there is one
$si = stripos($str,"/");
if($si == 0){
$str = substr($str,1);
}
// Remove everything after the subdir (if there is anything)
$lastpart = strchr($str,"/");
$str = str_replace($lastpart,"",$str);
return $str;
}
As you can see, it's a little hacky in order to handle some odd cases (no '/' in input, empty input, etc). I hope all that made sense. Any help/suggestions are welcome.
Update (altered solution):
Well Alix Axel had it spot on. Here's his solution with slight tweaks so that it matches my exact requirements (eg: it must return a string, only directories should be outputted (not files))
function getsubdir($str,$dir) {
$str = dirname($str);
$temp = array_slice(array_diff(explode('/', $str), explode('/', $dir)), 0, 1);
return $temp[0];
}
Here you go:
function getSubDir($dir, $sub)
{
return array_slice(array_diff(explode('/', $dir), explode('/', $sub)), 0, 1);
}
EDIT - Foolproof implementation:
function getSubDirFoolproof($dir, $sub)
{
/*
This is the ONLY WAY we have to make SURE that the
last segment of $dir is a file and not a directory.
*/
if (is_file($dir))
{
$dir = dirname($dir);
}
// Is it necessary to convert to the fully expanded path?
$dir = realpath($dir);
$sub = realpath($sub);
// Do we need to worry about Windows?
$dir = str_replace('\\', '/', $dir);
$sub = str_replace('\\', '/', $sub);
// Here we filter leading, trailing and consecutive slashes.
$dir = array_filter(explode('/', $dir));
$sub = array_filter(explode('/', $sub));
// All done!
return array_slice(array_diff($dir, $sub), 0, 1);
}
How about splitting the whole thing into an array:
$fullpath = explode("/", "dir1/dir2/dir3/dir4/filename.ext");
$fulldir = explode("/", "dir1/dir2");
// Will result in array("dir1","dir2","dir3", "dir4", "filename.ext");
// and array("dir1", "dir2");
you should then be able to use array_diff():
$remainder = array_diff($fullpath, $fulldir);
// Should return array("dir3", "dir4", "filename.ext");
then, getting the direct child is easy:
echo $remainder[0];
I can't test this right now but it should work.
Here's a similar "short" solution, this time using string functions rather than array functions. If there is no corresponding part to be gotten from the string, getsubdir will return FALSE. The strtr segment is a quick way to escape the percents, which have special meaning to sscanf.
function getsubdir($str, $dir) {
return sscanf($str, strtr($dir, '%', '%%').'/%[^/]', $name) === 1 ? $name : FALSE;
}
And a quick test so you can see how it behaves:
$str = "dir1/dir2/dir3/dir4/filename.ext";
var_dump(
getSubDir($str, "dir1"),
getSubDir($str, "dir1/dir2/dir3"),
getSubDir($str, "cake")
);
// string(4) "dir2"
// string(4) "dir4"
// bool(false)
I often find that I have files in my projects that need to be accessed from the file system as well as the users browser. One example is uploading photos. I need access to the files on the file system so that I can use GD to alter the images or move them around. But my users also need to be able to access the files from a URL like example.com/uploads/myphoto.jpg.
Because the upload path usually corresponds to the URL I made up a function that seems to work most of the time. Take these paths for example:
File System
/var/www/example.com/uploads/myphoto.jpg
URL
http://example.com/uploads/myphoto.jpg
If I had a variable set to something like /var/www/example.com/ then I could subtract it from the filesystem path and then use it as the URL to the image.
/**
* Remove a given file system path from the file/path string.
* If the file/path does not contain the given path - return FALSE.
* #param string $file
* #param string $path
* #return mixed
*/
function remove_path($file, $path = UPLOAD_PATH) {
if(strpos($file, $path) !== FALSE) {
return substr($file, strlen($path));
}
}
$file = /var/www/example.com/uploads/myphoto.jpg;
print remove_path($file, /var/www/site.com/);
//prints "uploads/myphoto.jpg"
Does anyone know of a better way to handle this?
More accurate way (including host port) would be to use this
function path2url($file, $Protocol='http://') {
return $Protocol.$_SERVER['HTTP_HOST'].str_replace($_SERVER['DOCUMENT_ROOT'], '', $file);
}
Assume the directory is /path/to/root/document_root/user/file and the address is site.com/user/file
The first function I am showing will get the current file's name relative to the World Wide Web Address.
$path = $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF'];
and would result in:
site.com/user/file
The second function strips the given path of the document root.
$path = str_replace($_SERVER['DOCUMENT_ROOT'], '', $path)
Given I passed in /path/to/root/document_root/user/file, I would get
/user/file
IMHO such automation is really error prone. You're far better off using some explicit path helpers (eg. one for uploads, one for user pics, etc) or just encapsulate for example an uploaded file with a class.
// Some "pseudo code"
$file = UploadedFile::copy($_FILES['foo']);
$file->getPath(); // /var/www/example.org/uploads/foo.ext
$file->getUri(); // http://example.org/uploads/foo.ext
Make it easy on yourself and just define the correct locations for both the filesystem and web folders and prepend the image filename with them.
Somewhere, you'd declare:
define('PATH_IMAGES_FS', '/var/www/example.com/uploads/');
define('PATH_IMAGES_WEB', 'uploads/');
Then you can just swap between paths depending on your need:
$image_file = 'myphoto.jpg';
$file = PATH_IMAGES_FS.$image_file;
//-- stores: /var/www/example.com/uploads/myphoto.jpg
print PATH_IMAGES_WEB.$image_file;
//-- prints: uploads/myphoto.jpg
Try this:
$imgUrl = str_replace($_SERVER['DOCUMENT_ROOT'], '', $imgPath)
I've used this and worked with me:
$file_path=str_replace('\\','/',__file__);
$file_path=str_replace($_SERVER['DOCUMENT_ROOT'],'',$file_path);
$path='http://'.$_SERVER['HTTP_HOST'].'/'.$file_path;
And if you need the directory name in url format add this line:
define('URL_DIR',dirname($path));
The code below is well commented:
function pathToURL($path) {
//Replace backslashes to slashes if exists, because no URL use backslashes
$path = str_replace("\\", "/", realpath($path));
//if the $path does not contain the document root in it, then it is not reachable
$pos = strpos($path, $_SERVER['DOCUMENT_ROOT']);
if ($pos === false) return false;
//just cut the DOCUMENT_ROOT part of the $path
return substr($path, strlen($_SERVER['DOCUMENT_ROOT']));
//Note: usually /images is the same with http://somedomain.com/images,
// So let's not bother adding domain name here.
}
echo pathToURL('some/path/on/public/html');
For example, i used this one to convert C:\WAMP\WWW\myfolder\document.txt to http://example.com/myfolder/document.txt use this one:
$file_path=str_replace('\\','/',$file_path);
$file_path=str_replace($_SERVER['DOCUMENT_ROOT'],'',$file_path);
$file_path='http://'.$_SERVER['HTTP_HOST'].$file_path;
This simple snippet can convert the file path to file's url on the server. Some settings like protocol and port should be kept.
$filePath = str_replace('\\','/',$filePath);
$ssl = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? true : false;
$sp = strtolower($_SERVER['SERVER_PROTOCOL']);
$protocol = substr($sp, 0, strpos($sp, '/')) . (($ssl) ? 's' : '');
$port = $_SERVER['SERVER_PORT'];
$stringPort = ((!$ssl && $port == '80') || ($ssl && $port == '443')) ? '' : ':' . $port;
$host = isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'];
$fileUrl = str_replace($_SERVER['DOCUMENT_ROOT'] ,$protocol . '://' . $host . $stringPort, $filePath);
I always use symlinks in my local development environment and #George's approach fails in this case:
The DOCUMENT_ROOT is set to /Library/WebServer/Documents and there is a symlink /Library/WebServer/Documents/repo1 -> /Users/me/dev/web/repo1
Assume that following codes are in /Users/me/dev/web/repo1/example.php
$_SERVER['DOCUMENT_ROOT'] == "/Library/WebServer/Documents" //default on OS X
while
realpath('./some/relative.file') == "/Users/me/dev/web/repo1/some/relative.file"
Thus, replacing DOCUMENT_ROOT with HTTP_HOST doesn't work.
I come up with this little trick:
function path2url($path) {
$pos = strrpos(__FILE__, $_SERVER['PHP_SELF']);
return substr(realpath($path), $pos);
}
// where
__FILE__ == "/Users/me/dev/web/repo1/example.php"
$_SERVER['PHP_SELF'] == "/web/repo1/example.php"
realpath("./some/relative.file") == "/Users/me/dev/web/repo1/some/relative.file"
// If I cut off the pre-fix part from realpath($path),
// the remainder will be full path relative to virtual host root
path2url("./some/relative.file") == "/web/repo1/some/relative.file"
I think it's good practice to fore-prevent the potential bugs even we are not likely to use symlinks in production environment.
All answers here promotes str_replace() which replaces all occurences anywhere in the string, not just in the beginning. preg_replace() will make sure we only do an exact match from the beginning of the string:
function remove_path($file, $path = UPLOAD_PATH) {
return preg_replace("#^($path)#", '', $file);
}
Windows can be a problem where directory separators / and \. Make sure you replace the directory separators first:
function remove_path($file, $path = UPLOAD_PATH) {
$file = preg_replace("#([\\\\/]+)#", '/', $file);
$path = preg_replace("#([\\\\/]+)#", '/', $path);
return preg_replace("#^($path)#", '', $file);
}
I would play with something like the following. Make note of realpath() and rtrim().
function webpath($file) {
$document_root = rtrim(preg_replace("#([\\\\/]+)#", '/', $_SERVER['DOCUMENT_ROOT']), '/');
$file = preg_replace("#([\\\\/]+)#", '/', realpath($file));
return preg_replace("#^($document_root)#", '', $file);
}
echo webpath(__FILE__); // Returns webpath to self
echo webpath('../file.ext'); // Relative paths
echo webpath('/full/path/to/file.ext'); // Full paths
One row complete solution to the "convert path to url" problem (*):
$path = "/web/htdocs/<domain>/home/path/to/file/file.ext";
$url = ( ( isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1) || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' )? 'https://' : 'http://' ).$_SERVER['HTTP_HOST'].str_replace(realpath($_SERVER['DOCUMENT_ROOT']), '', realpath($ITEM_GPX_PATH));
echo $url;
// print "http(s)://<domain>/path/to/file/file.ext"
I came to this solution thanks to the answers of George (and the respective comments of Stephanie and SWHarden) and of Rid Iculous.
Note: my solution does not answer the complete question directly, but its title; I thought to insert this answer because it can be useful to those who search on Google "php convert path to url"
I need is_dir() to don't care about if the folder or the argument is upper or lower case, or a mix of upper and lower.
So if the foldername is "My Folder" and I run is_dir("mY FoLdEr") the result should be true.
That's not up to PHP. That's up to operating system and filesystem that PHP is running on. Windows is case insensitive but every version of Unix/Linux is case sensitive.
Let me put it another way: is_dir() is basically a wrapper to a system call or it will use the results of system calls on file info. Those system calls will either return something or not if, by the rules of that operating system and filesystem, a file is found with the matching name. PHP can't change this so no you can't just make is_dir() be case insensitive on Linux.
The best you can do is get a list of files in the directory and loop through them to test to see if any match a case insensitive comparison to what you're looking for.
Note: you might get multiple hits eg "HOME" and "Home" will both match "home". What would such a function (that you want) do in this case?
Alternatively you can change all your filenames to lowercase and then you can use the lowercase version of your input to find the specified file.
You can use regular expressions. I am not quite sure on the syntax in php:
is_dir("[mM][yY] [fF][oO][lL][dD][eE][rR]")
They may be a better regexp for it.
I had an issue where I needed to validate a directory path. I didn't want to have a mixture of similarly name directories in different cases. i.e. my\dir and My\Dir. I tried the grep approach mentioned by Filip glob('{mM}{yY} {fF}{oO}{lL}{dD}{eE}{rR}', GLOB_BRACE) but I found that if the directory name was longer than about 8 characters it would grind to a halt. So this is my solution for a case insensitive is_dir();
$path = '/';
$parts = explode(DIRECTORY_SEPARATOR, '/My/DirecTorY/pATh');
foreach($parts as $key => $dir) {
$isUnique = true;
if (is_dir($path . DIRECTORY_SEPARATOR . $dir)) {
$path .= DIRECTORY_SEPARATOR . $dir;
$isUnique = false;
} else {
$iterator = new DirectoryIterator($path);
$name = strtolower($dir);
foreach($iterator as $file) {
$filename = $file->getFilename();
if($file->isDir() && strtolower($filename) == $name) {
$path .= DIRECTORY_SEPARATOR . $filename;
$isUnique = false;
break;
}
}
}
if($isUnique) {
$path .= DIRECTORY_SEPARATOR
. implode(DIRECTORY_SEPARATOR, array_slice($parts, $key));
break;
}
}
var_dump($isUnique, $path);
Dirty way could be getting list of all directories in actual dir and compare theirs strtolower()-ed names with desired name
here is my solution:
function is_dir_ci($path){
$glob_path='';
for ($i=0; $i<strlen($path); $i++) {
if(preg_match('/^\p{Latin}+$/',$path[$i])){
$glob_path.='['.strtolower($path[$i]).strtoupper($path[$i]).']';
}else
$glob_path.=$path[$i];
}
return !empty(glob($glob_path,GLOB_BRACE));
}
is_dir_ci('/path/With/Cap-Case or not/');
it basicaly does transform the path from
'/path/With/Cap-Case or not/'
to
'/[pP][aA][tT][hH]/[wW][iI][tT][hH]/[cC][aA][pP]-[cC][aA][sS][eE] [oO][rR] [nN][oO][tT]/'
But as only knowing there is dir with some cap-letters changed, I think a better function should be:
function get_correct_dir($path){
$glob_path='';
for ($i=0; $i<strlen($path); $i++) {
if(preg_match('/^\p{Latin}+$/',$path[$i])){
$glob_path.='['.strtolower($path[$i]).strtoupper($path[$i]).']';
}else
$glob_path.=$path[$i];
}
return glob($glob_path,GLOB_BRACE);
}
var_export( get_correct_dir('/path/With/Cap-Case or not/') )
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.
$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.
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));
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.
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 ""
You can just concatenate it with the php constant DIRECTORY_SEPARATOR, or just use forward slashes. Windows probably won't mind =D
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
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/"