class Path
Same name in this branch
- 11.1.x vendor/php-tuf/composer-stager/src/Internal/Path/Value/Path.php \PhpTuf\ComposerStager\Internal\Path\Value\Path
Contains utility methods for handling path strings.
The methods in this class are able to deal with both UNIX and Windows paths with both forward and backward slashes. All methods return normalized parts containing only forward slashes and no excess "." and ".." segments.
@author Bernhard Schussek <bschussek@gmail.com> @author Thomas Schulz <mail@king2500.net> @author Théo Fidry <theo.fidry@gmail.com>
Hierarchy
- class \Symfony\Component\Filesystem\Path
Expanded class hierarchy of Path
2 files declare their use of Path
- PathHelper.php in vendor/
php-tuf/ composer-stager/ src/ Internal/ Path/ Service/ PathHelper.php - UnknownPathExcluder.php in core/
modules/ package_manager/ src/ PathExcluder/ UnknownPathExcluder.php
111 string references to 'Path'
- AliasRepository::preloadPathAlias in core/
modules/ path_alias/ src/ AliasRepository.php - CliDumper::style in vendor/
symfony/ var-dumper/ Dumper/ CliDumper.php - Decorates a value with some style.
- contact.schema.yml in core/
modules/ contact/ config/ schema/ contact.schema.yml - core/modules/contact/config/schema/contact.schema.yml
- core.data_types.schema.yml in core/
config/ schema/ core.data_types.schema.yml - core/config/schema/core.data_types.schema.yml
- core.entity_form_display.media.audio.default.yml in core/
profiles/ standard/ config/ optional/ core.entity_form_display.media.audio.default.yml - core/profiles/standard/config/optional/core.entity_form_display.media.audio.default.yml
File
-
vendor/
symfony/ filesystem/ Path.php, line 28
Namespace
Symfony\Component\FilesystemView source
final class Path {
/**
* The number of buffer entries that triggers a cleanup operation.
*/
private const CLEANUP_THRESHOLD = 1250;
/**
* The buffer size after the cleanup operation.
*/
private const CLEANUP_SIZE = 1000;
/**
* Buffers input/output of {@link canonicalize()}.
*
* @var array<string, string>
*/
private static array $buffer = [];
private static int $bufferSize = 0;
/**
* Canonicalizes the given path.
*
* During normalization, all slashes are replaced by forward slashes ("/").
* Furthermore, all "." and ".." segments are removed as far as possible.
* ".." segments at the beginning of relative paths are not removed.
*
* ```php
* echo Path::canonicalize("\symfony\puli\..\css\style.css");
* // => /symfony/css/style.css
*
* echo Path::canonicalize("../css/./style.css");
* // => ../css/style.css
* ```
*
* This method is able to deal with both UNIX and Windows paths.
*/
public static function canonicalize(string $path) : string {
if ('' === $path) {
return '';
}
// 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 = self::getHomeDirectory() . substr($path, 1);
}
$path = self::normalize($path);
[
$root,
$pathWithoutRoot,
] = self::split($path);
$canonicalParts = self::findCanonicalParts($root, $pathWithoutRoot);
// 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;
}
/**
* Normalizes the given path.
*
* During normalization, all slashes are replaced by forward slashes ("/").
* Contrary to {@link canonicalize()}, this method does not remove invalid
* or dot path segments. Consequently, it is much more efficient and should
* be used whenever the given path is known to be a valid, absolute system
* path.
*
* This method is able to deal with both UNIX and Windows paths.
*/
public static function normalize(string $path) : string {
return str_replace('\\', '/', $path);
}
/**
* Returns the directory part of the path.
*
* This method is similar to PHP's dirname(), but handles various cases
* where dirname() returns a weird result:
*
* - dirname() does not accept backslashes on UNIX
* - dirname("C:/symfony") returns "C:", not "C:/"
* - dirname("C:/") returns ".", not "C:/"
* - dirname("C:") returns ".", not "C:/"
* - dirname("symfony") returns ".", not ""
* - dirname() does not canonicalize the result
*
* This method fixes these shortcomings and behaves like dirname()
* otherwise.
*
* The result is a canonical path.
*
* @return string The canonical directory part. Returns the root directory
* if the root directory is passed. Returns an empty string
* if a relative path is passed that contains no slashes.
* Returns an empty string if an empty string is passed.
*/
public static function getDirectory(string $path) : string {
if ('' === $path) {
return '';
}
$path = self::canonicalize($path);
// Maintain scheme
if (false !== ($schemeSeparatorPosition = strpos($path, '://'))) {
$scheme = substr($path, 0, $schemeSeparatorPosition + 3);
$path = substr($path, $schemeSeparatorPosition + 3);
}
else {
$scheme = '';
}
if (false === ($dirSeparatorPosition = strrpos($path, '/'))) {
return '';
}
// Directory equals root directory "/"
if (0 === $dirSeparatorPosition) {
return $scheme . '/';
}
// Directory equals Windows root "C:/"
if (2 === $dirSeparatorPosition && ctype_alpha($path[0]) && ':' === $path[1]) {
return $scheme . substr($path, 0, 3);
}
return $scheme . substr($path, 0, $dirSeparatorPosition);
}
/**
* Returns canonical path of the user's home directory.
*
* Supported operating systems:
*
* - UNIX
* - Windows8 and upper
*
* If your operating system or environment isn't supported, an exception is thrown.
*
* The result is a canonical path.
*
* @throws RuntimeException If your operating system or environment isn't supported
*/
public static function getHomeDirectory() : string {
// For UNIX support
if (getenv('HOME')) {
return self::canonicalize(getenv('HOME'));
}
// For >= Windows8 support
if (getenv('HOMEDRIVE') && getenv('HOMEPATH')) {
return self::canonicalize(getenv('HOMEDRIVE') . getenv('HOMEPATH'));
}
throw new RuntimeException("Cannot find the home directory path: Your environment or operating system isn't supported.");
}
/**
* Returns the root directory of a path.
*
* The result is a canonical path.
*
* @return string The canonical root directory. Returns an empty string if
* the given path is relative or empty.
*/
public static function getRoot(string $path) : string {
if ('' === $path) {
return '';
}
// Maintain scheme
if (false !== ($schemeSeparatorPosition = strpos($path, '://'))) {
$scheme = substr($path, 0, $schemeSeparatorPosition + 3);
$path = substr($path, $schemeSeparatorPosition + 3);
}
else {
$scheme = '';
}
$firstCharacter = $path[0];
// UNIX root "/" or "\" (Windows style)
if ('/' === $firstCharacter || '\\' === $firstCharacter) {
return $scheme . '/';
}
$length = \strlen($path);
// Windows root
if ($length > 1 && ':' === $path[1] && ctype_alpha($firstCharacter)) {
// Special case: "C:"
if (2 === $length) {
return $scheme . $path . '/';
}
// Normal case: "C:/ or "C:\"
if ('/' === $path[2] || '\\' === $path[2]) {
return $scheme . $firstCharacter . $path[1] . '/';
}
}
return '';
}
/**
* Returns the file name without the extension from a file path.
*
* @param string|null $extension if specified, only that extension is cut
* off (may contain leading dot)
*/
public static function getFilenameWithoutExtension(string $path, ?string $extension = null) : string {
if ('' === $path) {
return '';
}
if (null !== $extension) {
// remove extension and trailing dot
return rtrim(basename($path, $extension), '.');
}
return pathinfo($path, \PATHINFO_FILENAME);
}
/**
* Returns the extension from a file path (without leading dot).
*
* @param bool $forceLowerCase forces the extension to be lower-case
*/
public static function getExtension(string $path, bool $forceLowerCase = false) : string {
if ('' === $path) {
return '';
}
$extension = pathinfo($path, \PATHINFO_EXTENSION);
if ($forceLowerCase) {
$extension = self::toLower($extension);
}
return $extension;
}
/**
* Returns whether the path has an (or the specified) extension.
*
* @param string $path the path string
* @param string|string[]|null $extensions if null or not provided, checks if
* an extension exists, otherwise
* checks for the specified extension
* or array of extensions (with or
* without leading dot)
* @param bool $ignoreCase whether to ignore case-sensitivity
*/
public static function hasExtension(string $path, $extensions = null, bool $ignoreCase = false) : bool {
if ('' === $path) {
return false;
}
$actualExtension = self::getExtension($path, $ignoreCase);
// Only check if path has any extension
if ([] === $extensions || null === $extensions) {
return '' !== $actualExtension;
}
if (\is_string($extensions)) {
$extensions = [
$extensions,
];
}
foreach ($extensions as $key => $extension) {
if ($ignoreCase) {
$extension = self::toLower($extension);
}
// remove leading '.' in extensions array
$extensions[$key] = ltrim($extension, '.');
}
return \in_array($actualExtension, $extensions, true);
}
/**
* Changes the extension of a path string.
*
* @param string $path The path string with filename.ext to change.
* @param string $extension new extension (with or without leading dot)
*
* @return string the path string with new file extension
*/
public static function changeExtension(string $path, string $extension) : string {
if ('' === $path) {
return '';
}
$actualExtension = self::getExtension($path);
$extension = ltrim($extension, '.');
// No extension for paths
if (str_ends_with($path, '/')) {
return $path;
}
// No actual extension in path
if (!$actualExtension) {
return $path . (str_ends_with($path, '.') ? '' : '.') . $extension;
}
return substr($path, 0, -\strlen($actualExtension)) . $extension;
}
public static function isAbsolute(string $path) : bool {
if ('' === $path) {
return false;
}
// Strip scheme
if (false !== ($schemeSeparatorPosition = strpos($path, '://')) && 1 !== $schemeSeparatorPosition) {
$path = substr($path, $schemeSeparatorPosition + 3);
}
$firstCharacter = $path[0];
// UNIX root "/" or "\" (Windows style)
if ('/' === $firstCharacter || '\\' === $firstCharacter) {
return true;
}
// Windows root
if (\strlen($path) > 1 && ctype_alpha($firstCharacter) && ':' === $path[1]) {
// Special case: "C:"
if (2 === \strlen($path)) {
return true;
}
// Normal case: "C:/ or "C:\"
if ('/' === $path[2] || '\\' === $path[2]) {
return true;
}
}
return false;
}
public static function isRelative(string $path) : bool {
return !self::isAbsolute($path);
}
/**
* Turns a relative path into an absolute path in canonical form.
*
* Usually, the relative path is appended to the given base path. Dot
* segments ("." and "..") are removed/collapsed and all slashes turned
* into forward slashes.
*
* ```php
* echo Path::makeAbsolute("../style.css", "/symfony/puli/css");
* // => /symfony/puli/style.css
* ```
*
* If an absolute path is passed, that path is returned unless its root
* directory is different than the one of the base path. In that case, an
* exception is thrown.
*
* ```php
* Path::makeAbsolute("/style.css", "/symfony/puli/css");
* // => /style.css
*
* Path::makeAbsolute("C:/style.css", "C:/symfony/puli/css");
* // => C:/style.css
*
* Path::makeAbsolute("C:/style.css", "/symfony/puli/css");
* // InvalidArgumentException
* ```
*
* If the base path is not an absolute path, an exception is thrown.
*
* The result is a canonical path.
*
* @param string $basePath an absolute base path
*
* @throws InvalidArgumentException if the base path is not absolute or if
* the given path is an absolute path with
* a different root than the base path
*/
public static function makeAbsolute(string $path, string $basePath) : string {
if ('' === $basePath) {
throw new InvalidArgumentException(\sprintf('The base path must be a non-empty string. Got: "%s".', $basePath));
}
if (!self::isAbsolute($basePath)) {
throw new InvalidArgumentException(\sprintf('The base path "%s" is not an absolute path.', $basePath));
}
if (self::isAbsolute($path)) {
return self::canonicalize($path);
}
if (false !== ($schemeSeparatorPosition = strpos($basePath, '://'))) {
$scheme = substr($basePath, 0, $schemeSeparatorPosition + 3);
$basePath = substr($basePath, $schemeSeparatorPosition + 3);
}
else {
$scheme = '';
}
return $scheme . self::canonicalize(rtrim($basePath, '/\\') . '/' . $path);
}
/**
* Turns a path into a relative path.
*
* The relative path is created relative to the given base path:
*
* ```php
* echo Path::makeRelative("/symfony/style.css", "/symfony/puli");
* // => ../style.css
* ```
*
* If a relative path is passed and the base path is absolute, the relative
* path is returned unchanged:
*
* ```php
* Path::makeRelative("style.css", "/symfony/puli/css");
* // => style.css
* ```
*
* If both paths are relative, the relative path is created with the
* assumption that both paths are relative to the same directory:
*
* ```php
* Path::makeRelative("style.css", "symfony/puli/css");
* // => ../../../style.css
* ```
*
* If both paths are absolute, their root directory must be the same,
* otherwise an exception is thrown:
*
* ```php
* Path::makeRelative("C:/symfony/style.css", "/symfony/puli");
* // InvalidArgumentException
* ```
*
* If the passed path is absolute, but the base path is not, an exception
* is thrown as well:
*
* ```php
* Path::makeRelative("/symfony/style.css", "symfony/puli");
* // InvalidArgumentException
* ```
*
* If the base path is not an absolute path, an exception is thrown.
*
* The result is a canonical path.
*
* @throws InvalidArgumentException if the base path is not absolute or if
* the given path has a different root
* than the base path
*/
public static function makeRelative(string $path, string $basePath) : string {
$path = self::canonicalize($path);
$basePath = self::canonicalize($basePath);
[
$root,
$relativePath,
] = self::split($path);
[
$baseRoot,
$relativeBasePath,
] = self::split($basePath);
// If the base path is given as absolute path and the path is already
// relative, consider it to be relative to the given absolute path
// already
if ('' === $root && '' !== $baseRoot) {
// If base path is already in its root
if ('' === $relativeBasePath) {
$relativePath = ltrim($relativePath, './\\');
}
return $relativePath;
}
// If the passed path is absolute, but the base path is not, we
// cannot generate a relative path
if ('' !== $root && '' === $baseRoot) {
throw new InvalidArgumentException(\sprintf('The absolute path "%s" cannot be made relative to the relative path "%s". You should provide an absolute base path instead.', $path, $basePath));
}
// Fail if the roots of the two paths are different
if ($baseRoot && $root !== $baseRoot) {
throw new InvalidArgumentException(\sprintf('The path "%s" cannot be made relative to "%s", because they have different roots ("%s" and "%s").', $path, $basePath, $root, $baseRoot));
}
if ('' === $relativeBasePath) {
return $relativePath;
}
// Build a "../../" prefix with as many "../" parts as necessary
$parts = explode('/', $relativePath);
$baseParts = explode('/', $relativeBasePath);
$dotDotPrefix = '';
// Once we found a non-matching part in the prefix, we need to add
// "../" parts for all remaining parts
$match = true;
foreach ($baseParts as $index => $basePart) {
if ($match && isset($parts[$index]) && $basePart === $parts[$index]) {
unset($parts[$index]);
continue;
}
$match = false;
$dotDotPrefix .= '../';
}
return rtrim($dotDotPrefix . implode('/', $parts), '/');
}
/**
* Returns whether the given path is on the local filesystem.
*/
public static function isLocal(string $path) : bool {
return '' !== $path && !str_contains($path, '://');
}
/**
* Returns the longest common base path in canonical form of a set of paths or
* `null` if the paths are on different Windows partitions.
*
* Dot segments ("." and "..") are removed/collapsed and all slashes turned
* into forward slashes.
*
* ```php
* $basePath = Path::getLongestCommonBasePath(
* '/symfony/css/style.css',
* '/symfony/css/..'
* );
* // => /symfony
* ```
*
* The root is returned if no common base path can be found:
*
* ```php
* $basePath = Path::getLongestCommonBasePath(
* '/symfony/css/style.css',
* '/puli/css/..'
* );
* // => /
* ```
*
* If the paths are located on different Windows partitions, `null` is
* returned.
*
* ```php
* $basePath = Path::getLongestCommonBasePath(
* 'C:/symfony/css/style.css',
* 'D:/symfony/css/..'
* );
* // => null
* ```
*/
public static function getLongestCommonBasePath(string ...$paths) : ?string {
[
$bpRoot,
$basePath,
] = self::split(self::canonicalize(reset($paths)));
for (next($paths); null !== key($paths) && '' !== $basePath; next($paths)) {
[
$root,
$path,
] = self::split(self::canonicalize(current($paths)));
// If we deal with different roots (e.g. C:/ vs. D:/), it's time
// to quit
if ($root !== $bpRoot) {
return null;
}
// Make the base path shorter until it fits into path
while (true) {
if ('.' === $basePath) {
// No more base paths
$basePath = '';
// next path
continue 2;
}
// Prevent false positives for common prefixes
// see isBasePath()
if (str_starts_with($path . '/', $basePath . '/')) {
// next path
continue 2;
}
$basePath = \dirname($basePath);
}
}
return $bpRoot . $basePath;
}
/**
* Joins two or more path strings into a canonical path.
*/
public static function join(string ...$paths) : string {
$finalPath = null;
$wasScheme = false;
foreach ($paths as $path) {
if ('' === $path) {
continue;
}
if (null === $finalPath) {
// For first part we keep slashes, like '/top', 'C:\' or 'phar://'
$finalPath = $path;
$wasScheme = str_contains($path, '://');
continue;
}
// Only add slash if previous part didn't end with '/' or '\'
if (!\in_array(substr($finalPath, -1), [
'/',
'\\',
], true)) {
$finalPath .= '/';
}
// If first part included a scheme like 'phar://' we allow \current part to start with '/', otherwise trim
$finalPath .= $wasScheme ? $path : ltrim($path, '/');
$wasScheme = false;
}
if (null === $finalPath) {
return '';
}
return self::canonicalize($finalPath);
}
/**
* Returns whether a path is a base path of another path.
*
* Dot segments ("." and "..") are removed/collapsed and all slashes turned
* into forward slashes.
*
* ```php
* Path::isBasePath('/symfony', '/symfony/css');
* // => true
*
* Path::isBasePath('/symfony', '/symfony');
* // => true
*
* Path::isBasePath('/symfony', '/symfony/..');
* // => false
*
* Path::isBasePath('/symfony', '/puli');
* // => false
* ```
*/
public static function isBasePath(string $basePath, string $ofPath) : bool {
$basePath = self::canonicalize($basePath);
$ofPath = self::canonicalize($ofPath);
// Append slashes to prevent false positives when two paths have
// a common prefix, for example /base/foo and /base/foobar.
// Don't append a slash for the root "/", because then that root
// won't be discovered as common prefix ("//" is not a prefix of
// "/foobar/").
return str_starts_with($ofPath . '/', rtrim($basePath, '/') . '/');
}
/**
* @return string[]
*/
private static function findCanonicalParts(string $root, string $pathWithoutRoot) : array {
$parts = explode('/', $pathWithoutRoot);
$canonicalParts = [];
// 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;
}
}
return $canonicalParts;
}
/**
* Splits a canonical path into its root directory and the remainder.
*
* If the path has no root directory, an empty root directory will be
* returned.
*
* If the root directory is a Windows style partition, the resulting root
* will always contain a trailing slash.
*
* list ($root, $path) = Path::split("C:/symfony")
* // => ["C:/", "symfony"]
*
* list ($root, $path) = Path::split("C:")
* // => ["C:/", ""]
*
* @return array{string, string} an array with the root directory and the remaining relative path
*/
private static function split(string $path) : array {
if ('' === $path) {
return [
'',
'',
];
}
// Remember scheme as part of the root, if any
if (false !== ($schemeSeparatorPosition = strpos($path, '://'))) {
$root = substr($path, 0, $schemeSeparatorPosition + 3);
$path = substr($path, $schemeSeparatorPosition + 3);
}
else {
$root = '';
}
$length = \strlen($path);
// Remove and remember root directory
if (str_starts_with($path, '/')) {
$root .= '/';
$path = $length > 1 ? substr($path, 1) : '';
}
elseif ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) {
if (2 === $length) {
// Windows special case: "C:"
$root .= $path . '/';
$path = '';
}
elseif ('/' === $path[2]) {
// Windows normal case: "C:/"..
$root .= substr($path, 0, 3);
$path = $length > 3 ? substr($path, 3) : '';
}
}
return [
$root,
$path,
];
}
private static function toLower(string $string) : string {
if (false !== ($encoding = mb_detect_encoding($string, null, true))) {
return mb_strtolower($string, $encoding);
}
return strtolower($string);
}
private function __construct() {
}
}
Members
Title Sort descending | Modifiers | Object type | Summary |
---|---|---|---|
Path::$buffer | private static | property | Buffers input/output of {@link canonicalize()}. |
Path::$bufferSize | private static | property | |
Path::canonicalize | public static | function | Canonicalizes the given path. |
Path::changeExtension | public static | function | Changes the extension of a path string. |
Path::CLEANUP_SIZE | private | constant | The buffer size after the cleanup operation. |
Path::CLEANUP_THRESHOLD | private | constant | The number of buffer entries that triggers a cleanup operation. |
Path::findCanonicalParts | private static | function | |
Path::getDirectory | public static | function | Returns the directory part of the path. |
Path::getExtension | public static | function | Returns the extension from a file path (without leading dot). |
Path::getFilenameWithoutExtension | public static | function | Returns the file name without the extension from a file path. |
Path::getHomeDirectory | public static | function | Returns canonical path of the user's home directory. |
Path::getLongestCommonBasePath | public static | function | Returns the longest common base path in canonical form of a set of paths or `null` if the paths are on different Windows partitions. |
Path::getRoot | public static | function | Returns the root directory of a path. |
Path::hasExtension | public static | function | Returns whether the path has an (or the specified) extension. |
Path::isAbsolute | public static | function | |
Path::isBasePath | public static | function | Returns whether a path is a base path of another path. |
Path::isLocal | public static | function | Returns whether the given path is on the local filesystem. |
Path::isRelative | public static | function | |
Path::join | public static | function | Joins two or more path strings into a canonical path. |
Path::makeAbsolute | public static | function | Turns a relative path into an absolute path in canonical form. |
Path::makeRelative | public static | function | Turns a path into a relative path. |
Path::normalize | public static | function | Normalizes the given path. |
Path::split | private static | function | Splits a canonical path into its root directory and the remainder. |
Path::toLower | private static | function | |
Path::__construct | private | function |