public static function canonicalize($path)
{
if ('' === $path) {
return '';
}
Assert::string($path, 'The path must be a string. Got: %s');
// This method is called by many other methods in this class. Buffer
// the canonicalized paths to make up for the severe performance
// decrease.
if (isset(self::$buffer[$path])) {
return self::$buffer[$path];
}
// Replace "~" with user's home directory.
if ('~' === $path[0]) {
$path = static::getHomeDirectory() . substr($path, 1);
}
$path = str_replace('\\', '/', $path);
list($root, $pathWithoutRoot) = self::split($path);
$parts = explode('/', $pathWithoutRoot);
$canonicalParts = array();
// Collapse "." and "..", if possible
foreach ($parts as $part) {
if ('.' === $part || '' === $part) {
continue;
}
// Collapse ".." with the previous part, if one exists
// Don't collapse ".." if the previous part is also ".."
if ('..' === $part && count($canonicalParts) > 0 && '..' !== $canonicalParts[count($canonicalParts) - 1]) {
array_pop($canonicalParts);
continue;
}
// Only add ".." prefixes for relative paths
if ('..' !== $part || '' === $root) {
$canonicalParts[] = $part;
}
}
// Add the root directory again
self::$buffer[$path] = $canonicalPath = $root . implode('/', $canonicalParts);
++self::$bufferSize;
// Clean up regularly to prevent memory leaks
if (self::$bufferSize > self::CLEANUP_THRESHOLD) {
self::$buffer = array_slice(self::$buffer, -self::CLEANUP_SIZE, null, true);
self::$bufferSize = self::CLEANUP_SIZE;
}
return $canonicalPath;
}