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