LiraNuna's Development Blog
PHP Path resolution class – Relative paths made easy
Posted on Saturday 5 December 2009

Lately I’ve been working on a project that required me to handle a lot of file-system related operations, especially paths.

While PHP offers some basic functions to handle paths, such as basename and dirname to resolute the (direct) parent and base name of a path, it does not offer any means of normalizing or combining a path if it’s on a remote file system that is not in the server’s reach. If the files are local, it offers the function realpath.

I didn’t like the case and decided to write a ‘static’ utility class to handle file paths safely, without worrying about possible path masquerading from broken code.

I hope someone will find the result useful:

<?php
 
/**
 * @class Path
 *
 * @brief Utility class that handles file and directory pathes
 *
 * This class handles basic important operations done to file system paths.
 * It safely renders relative pathes and removes all ambiguity from a relative path.
 *
 * @author Liran Nuna
 */
final class Path
{
	/**
	 * Returns the parent path of this path.
	 * "/path/to/directory" will return "/path/to"
	 *
	 * @arg $path	The path to retrieve the parent path from
	 */
	public static function dirname($path) {
		return dirname(self::normalize($path));
	}
 
	/**
	 * Returns the last item on the path.
	 * "/path/to/directory" will return "directory"
	 *
	 * @arg $path	The path to retrieve the base from
	 */
	public static function basename($path) {
		return basename(self::normalize($path));
	}
 
	/**
	 * Normalizes the path for safe usage
	 * This function does several operations to the given path:
	 *   * Removes unnecessary slashes (///path//to/////directory////)
	 *   * Removes current directory references (/path/././to/./directory/./././)
	 *   * Renders relative pathes (/path/from/../to/somewhere/in/../../directory)
	 *
	 * @arg $path	The path to normalize
	 */
	public static function normalize($path) {
		return array_reduce(explode('/', $path), create_function('$a, $b', '
			if($a === 0)
				$a = "/";
 
			if($b === "" || $b === ".")
				return $a;
 
			if($b === "..")
				return dirname($a);
 
			return preg_replace("/\/+/", "/", "$a/$b");
		'), 0);
	}
 
	/**
	 * Combines a list of pathes to one safe path
	 *
	 * @arg $root	The path or array with values to combine into a single path
	 * @arg ...		Relative pathes to root or arrays
	 *
	 * @note		This function works with multi-dimentional arrays recursively.
	 */
	public static function combine($root, $rel1) {
		$arguments = func_get_args();
		return self::normalize(array_reduce($arguments, create_function('$a,$b', '
			if(is_array($a))
				$a = array_reduce($a, "Path::combine");
			if(is_array($b))
				$b = array_reduce($b, "Path::combine");
 
			return "$a/$b";
		')));
	}
 
	/**
	 * Empty, private constructor, to prevent instantiation
	 */
	private function __construct() {
		// Prevents instantiation
	}
}

Usage of this class is very simple, Path::basename and Path::dirname perform the same operation as PHP’s native dirname and basename, but safer:

<?php
// PHP's native basname will return '..'
echo basename('/path/to/treasure/island/monster/../..') . "\n";
// Safe basename will return 'treasure'
echo Path::basename('/path/to/treasure/island/monster/../..') . "\n";
 
// PHP's native dirname will return '/path/to/treasure/island/monster/..'
echo dirname('/path/to/treasure/island/monster/../..') . "\n";
// Safe dirname will return '/path/to'
echo Path::dirname('/path/to/treasure/island/monster/../..') . "\n";

Path::normalize will sanitize paths and return the safe real path even if it does not exist on the server:

<?php
// Normalize will 'sanitize' this path
// Result: '/path/to/candy/up/ahead/please/go/right'
echo Path::normalize(
	'///../path//to/./monster/././/' .
	'//../candy/.//./up/ahead/.//./' .
	'test//back/../..//please/go///' .
	'/left/./../right/123_test!/../'
) . "\n";

Lastly, Path::combine will combine paths from variable amount of strings and arrays to form one safe path:

<?php
// Combine paths from a relative path and root
// Result: '/var/www/www.site.com/index.html'
echo Path::combine(
	'/var/www/www.site.com/',
	'img/../css/jqueryui/../../index.html'
) . "\n";
 
// Combine will also take values from arrays
// Result: '/path/to/directory/sub/TEST/test/lastDirectory/filename.ext'
echo Path::combine(
	array(
		"/path/to",
		"folder/../directory"
	), 'sub',
	array(
		array(
			array(
				'TEST',
				'test',
			)
		),
		'lastDirectory',
	),
	'filename.ext'
) . "\n";

As always, code I post is under the WTFPL, so you can use it without any obligations.


6 Comments for 'PHP Path resolution class – Relative paths made easy'

  1.  
    ctrout
    June 21, 2010 | 23:33
     

    Brilliant….been struggling on an app because of uri resolution issues and this has saved me several hours of additional work :-)

  2.  
    js
    July 5, 2010 | 2:59
     

    You saved my day :) — And a few hour’s code…

    Also I like the licence :) This way I can include your code in my public-domain-ish project :).

  3.  
    Matt
    December 3, 2010 | 0:13
     

    You rock. Path resolution was driving me nuts.

  4.  
    November 28, 2012 | 22:28
     

    This saved me a lot of time. Thanks.

    And thanks for pointing me to the WTFPL, I’d never come across that before.

  5.  
    Luca from Italy
    January 10, 2013 | 10:50
     

    The normalize function is AWESOME!!! You saved my day, many thanks!

  6.  
    rteroganestrevo
    March 7, 2014 | 2:05
     

    [b][url=http://fr

Leave a comment

(required)

(required)


Information for comment users
Line and paragraph breaks are implemented automatically. Your e-mail address is never displayed. Please consider what you're posting.

Use the buttons below to customise your comment.


RSS feed for comments on this post | TrackBack URI