Chris@0: Painfree Handling of File Paths Chris@0: =============================== Chris@0: Chris@0: Dealing with file paths usually involves some difficulties: Chris@0: Chris@0: * **System Heterogeneity**: File paths look different on different platforms. Chris@0: UNIX file paths start with a slash ("/"), while Windows file paths start with Chris@0: a system drive ("C:"). UNIX uses forward slashes, while Windows uses Chris@0: backslashes by default ("\"). Chris@0: Chris@0: * **Absolute/Relative Paths**: Web applications frequently need to deal with Chris@0: absolute and relative paths. Converting one to the other properly is tricky Chris@0: and repetitive. Chris@0: Chris@0: This package provides few, but robust utility methods to simplify your life Chris@0: when dealing with file paths. Chris@0: Chris@0: Canonicalization Chris@0: ---------------- Chris@0: Chris@0: *Canonicalization* is the transformation of a path into a normalized (the Chris@0: "canonical") format. You can canonicalize a path with `Path::canonicalize()`: Chris@0: Chris@0: ```php Chris@0: echo Path::canonicalize('/var/www/vhost/webmozart/../config.ini'); Chris@0: // => /var/www/vhost/config.ini Chris@0: ``` Chris@0: Chris@0: The following modifications happen during canonicalization: Chris@0: Chris@0: * "." segments are removed; Chris@0: * ".." segments are resolved; Chris@0: * backslashes ("\") are converted into forward slashes ("/"); Chris@0: * root paths ("/" and "C:/") always terminate with a slash; Chris@0: * non-root paths never terminate with a slash; Chris@0: * schemes (such as "phar://") are kept; Chris@0: * replace "~" with the user's home directory. Chris@0: Chris@0: You can pass absolute paths and relative paths to `canonicalize()`. When a Chris@0: relative path is passed, ".." segments at the beginning of the path are kept: Chris@0: Chris@0: ```php Chris@0: echo Path::canonicalize('../uploads/../config/config.yml'); Chris@0: // => ../config/config.yml Chris@0: ``` Chris@0: Chris@0: Malformed paths are returned unchanged: Chris@0: Chris@0: ```php Chris@0: echo Path::canonicalize('C:Programs/PHP/php.ini'); Chris@0: // => C:Programs/PHP/php.ini Chris@0: ``` Chris@0: Chris@0: Converting Absolute/Relative Paths Chris@0: ---------------------------------- Chris@0: Chris@0: Absolute/relative paths can be converted with the methods `Path::makeAbsolute()` Chris@0: and `Path::makeRelative()`. Chris@0: Chris@0: `makeAbsolute()` expects a relative path and a base path to base that relative Chris@0: path upon: Chris@0: Chris@0: ```php Chris@0: echo Path::makeAbsolute('config/config.yml', '/var/www/project'); Chris@0: // => /var/www/project/config/config.yml Chris@0: ``` Chris@0: Chris@0: If an absolute path is passed in the first argument, the absolute path is Chris@0: returned unchanged: Chris@0: Chris@0: ```php Chris@0: echo Path::makeAbsolute('/usr/share/lib/config.ini', '/var/www/project'); Chris@0: // => /usr/share/lib/config.ini Chris@0: ``` Chris@0: Chris@0: The method resolves ".." segments, if there are any: Chris@0: Chris@0: ```php Chris@0: echo Path::makeAbsolute('../config/config.yml', '/var/www/project/uploads'); Chris@0: // => /var/www/project/config/config.yml Chris@0: ``` Chris@0: Chris@0: This method is very useful if you want to be able to accept relative paths (for Chris@0: example, relative to the root directory of your project) and absolute paths at Chris@0: the same time. Chris@0: Chris@0: `makeRelative()` is the inverse operation to `makeAbsolute()`: Chris@0: Chris@0: ```php Chris@0: echo Path::makeRelative('/var/www/project/config/config.yml', '/var/www/project'); Chris@0: // => config/config.yml Chris@0: ``` Chris@0: Chris@0: If the path is not within the base path, the method will prepend ".." segments Chris@0: as necessary: Chris@0: Chris@0: ```php Chris@0: echo Path::makeRelative('/var/www/project/config/config.yml', '/var/www/project/uploads'); Chris@0: // => ../config/config.yml Chris@0: ``` Chris@0: Chris@0: Use `isAbsolute()` and `isRelative()` to check whether a path is absolute or Chris@0: relative: Chris@0: Chris@0: ```php Chris@0: Path::isAbsolute('C:\Programs\PHP\php.ini') Chris@0: // => true Chris@0: ``` Chris@0: Chris@0: All four methods internally canonicalize the passed path. Chris@0: Chris@0: Finding Longest Common Base Paths Chris@0: --------------------------------- Chris@0: Chris@0: When you store absolute file paths on the file system, this leads to a lot of Chris@0: duplicated information: Chris@0: Chris@0: ```php Chris@0: return array( Chris@0: '/var/www/vhosts/project/httpdocs/config/config.yml', Chris@0: '/var/www/vhosts/project/httpdocs/config/routing.yml', Chris@0: '/var/www/vhosts/project/httpdocs/config/services.yml', Chris@0: '/var/www/vhosts/project/httpdocs/images/banana.gif', Chris@0: '/var/www/vhosts/project/httpdocs/uploads/images/nicer-banana.gif', Chris@0: ); Chris@0: ``` Chris@0: Chris@0: Especially when storing many paths, the amount of duplicated information is Chris@0: noticeable. You can use `Path::getLongestCommonBasePath()` to check a list of Chris@0: paths for a common base path: Chris@0: Chris@0: ```php Chris@0: $paths = array( Chris@0: '/var/www/vhosts/project/httpdocs/config/config.yml', Chris@0: '/var/www/vhosts/project/httpdocs/config/routing.yml', Chris@0: '/var/www/vhosts/project/httpdocs/config/services.yml', Chris@0: '/var/www/vhosts/project/httpdocs/images/banana.gif', Chris@0: '/var/www/vhosts/project/httpdocs/uploads/images/nicer-banana.gif', Chris@0: ); Chris@0: Chris@0: Path::getLongestCommonBasePath($paths); Chris@0: // => /var/www/vhosts/project/httpdocs Chris@0: ``` Chris@0: Chris@0: Use this path together with `Path::makeRelative()` to shorten the stored paths: Chris@0: Chris@0: ```php Chris@0: $bp = '/var/www/vhosts/project/httpdocs'; Chris@0: Chris@0: return array( Chris@0: $bp.'/config/config.yml', Chris@0: $bp.'/config/routing.yml', Chris@0: $bp.'/config/services.yml', Chris@0: $bp.'/images/banana.gif', Chris@0: $bp.'/uploads/images/nicer-banana.gif', Chris@0: ); Chris@0: ``` Chris@0: Chris@0: `getLongestCommonBasePath()` always returns canonical paths. Chris@0: Chris@0: Use `Path::isBasePath()` to test whether a path is a base path of another path: Chris@0: Chris@0: ```php Chris@0: Path::isBasePath("/var/www", "/var/www/project"); Chris@0: // => true Chris@0: Chris@0: Path::isBasePath("/var/www", "/var/www/project/.."); Chris@0: // => true Chris@0: Chris@0: Path::isBasePath("/var/www", "/var/www/project/../.."); Chris@0: // => false Chris@0: ``` Chris@0: Chris@0: Finding Directories/Root Directories Chris@0: ------------------------------------ Chris@0: Chris@0: PHP offers the function `dirname()` to obtain the directory path of a file path. Chris@0: This method has a few quirks: Chris@0: Chris@0: * `dirname()` does not accept backslashes on UNIX Chris@0: * `dirname("C:/Programs")` returns "C:", not "C:/" Chris@0: * `dirname("C:/")` returns ".", not "C:/" Chris@0: * `dirname("C:")` returns ".", not "C:/" Chris@0: * `dirname("Programs")` returns ".", not "" Chris@0: * `dirname()` does not canonicalize the result Chris@0: Chris@0: `Path::getDirectory()` fixes these shortcomings: Chris@0: Chris@0: ```php Chris@0: echo Path::getDirectory("C:\Programs"); Chris@0: // => C:/ Chris@0: ``` Chris@0: Chris@0: Additionally, you can use `Path::getRoot()` to obtain the root of a path: Chris@0: Chris@0: ```php Chris@0: echo Path::getRoot("/etc/apache2/sites-available"); Chris@0: // => / Chris@0: Chris@0: echo Path::getRoot("C:\Programs\Apache\Config"); Chris@0: // => C:/ Chris@0: ``` Chris@0: