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