1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\File;
4:
5: use Nette\Utils\Strings;
6: use PHPStan\DependencyInjection\AutowiredParameter;
7: use PHPStan\DependencyInjection\AutowiredService;
8: use function array_pop;
9: use function explode;
10: use function implode;
11: use function ltrim;
12: use function preg_match;
13: use function rtrim;
14: use function str_ends_with;
15: use function str_replace;
16: use function str_starts_with;
17: use function strlen;
18: use function strtolower;
19: use function substr;
20: use function trim;
21: use const DIRECTORY_SEPARATOR;
22:
23: #[AutowiredService]
24: final class FileHelper
25: {
26:
27: private string $workingDirectory;
28:
29: public function __construct(
30: #[AutowiredParameter(ref: '%currentWorkingDirectory%')]
31: string $workingDirectory,
32: )
33: {
34: $this->workingDirectory = $this->normalizePath($workingDirectory);
35: }
36:
37: public function getWorkingDirectory(): string
38: {
39: return $this->workingDirectory;
40: }
41:
42: /** @api */
43: public function absolutizePath(string $path): string
44: {
45: if (DIRECTORY_SEPARATOR === '/') {
46: if (str_starts_with($path, '/')) {
47: return $path;
48: }
49: } elseif (substr($path, 1, 1) === ':') {
50: return $path;
51: }
52:
53: if (preg_match('~^[a-z0-9+\-.]+://~i', $path) === 1) {
54: return $path;
55: }
56:
57: return rtrim($this->getWorkingDirectory(), '/\\') . DIRECTORY_SEPARATOR . ltrim($path, '/\\');
58: }
59:
60: /** @api */
61: public function normalizePath(string $originalPath, string $directorySeparator = DIRECTORY_SEPARATOR): string
62: {
63: $isLocalPath = false;
64: if ($originalPath !== '') {
65: if ($originalPath[0] === '/') {
66: $isLocalPath = true;
67: } elseif (strlen($originalPath) >= 3 && $originalPath[1] === ':' && $originalPath[2] === '\\') { // e.g. C:\
68: $isLocalPath = true;
69: }
70: }
71:
72: $matches = null;
73: if (!$isLocalPath) {
74: $matches = Strings::match($originalPath, '~^([a-z0-9+\-.]+)://(.+)$~is');
75: }
76:
77: if ($matches !== null) {
78: [, $scheme, $path] = $matches;
79: $scheme = strtolower($scheme);
80: } else {
81: $scheme = null;
82: $path = $originalPath;
83: }
84:
85: $path = str_replace(['\\', '//', '///', '////'], '/', $path);
86:
87: $pathRoot = str_starts_with($path, '/') ? $directorySeparator : '';
88: $pathParts = explode('/', trim($path, '/'));
89:
90: $normalizedPathParts = [];
91: foreach ($pathParts as $pathPart) {
92: if ($pathPart === '.') {
93: continue;
94: }
95: if ($pathPart === '..') {
96: $removedPart = array_pop($normalizedPathParts);
97: if ($scheme === 'phar' && $removedPart !== null && str_ends_with($removedPart, '.phar')) {
98: $scheme = null;
99: }
100: } else {
101: $normalizedPathParts[] = $pathPart;
102: }
103: }
104:
105: return ($scheme !== null ? $scheme . '://' : '') . $pathRoot . implode($directorySeparator, $normalizedPathParts);
106: }
107:
108: }
109: