| 1: | <?php | 
| 2: |  | 
| 3: | declare(strict_types=1); | 
| 4: |  | 
| 5: | namespace PHPStan\BetterReflection\SourceLocator\Type\Composer\Factory; | 
| 6: |  | 
| 7: | use PHPStan\BetterReflection\SourceLocator\Ast\Locator; | 
| 8: | use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator; | 
| 9: | use PHPStan\BetterReflection\SourceLocator\Type\Composer\Factory\Exception\InvalidProjectDirectory; | 
| 10: | use PHPStan\BetterReflection\SourceLocator\Type\Composer\Factory\Exception\MissingComposerJson; | 
| 11: | use PHPStan\BetterReflection\SourceLocator\Type\Composer\Factory\Exception\MissingInstalledJson; | 
| 12: | use PHPStan\BetterReflection\SourceLocator\Type\Composer\Psr\Psr0Mapping; | 
| 13: | use PHPStan\BetterReflection\SourceLocator\Type\Composer\Psr\Psr4Mapping; | 
| 14: | use PHPStan\BetterReflection\SourceLocator\Type\Composer\PsrAutoloaderLocator; | 
| 15: | use PHPStan\BetterReflection\SourceLocator\Type\DirectoriesSourceLocator; | 
| 16: | use PHPStan\BetterReflection\SourceLocator\Type\SingleFileSourceLocator; | 
| 17: | use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; | 
| 18: |  | 
| 19: | use function array_filter; | 
| 20: | use function array_map; | 
| 21: | use function array_merge; | 
| 22: | use function array_merge_recursive; | 
| 23: | use function array_values; | 
| 24: | use function assert; | 
| 25: | use function file_get_contents; | 
| 26: | use function is_dir; | 
| 27: | use function is_file; | 
| 28: | use function is_string; | 
| 29: | use function json_decode; | 
| 30: | use function realpath; | 
| 31: | use function rtrim; | 
| 32: |  | 
| 33: | use const JSON_THROW_ON_ERROR; | 
| 34: |  | 
| 35: |  | 
| 36: |  | 
| 37: |  | 
| 38: |  | 
| 39: |  | 
| 40: | final class MakeLocatorForComposerJsonAndInstalledJson | 
| 41: | { | 
| 42: | public function __invoke(string $installationPath, Locator $astLocator): SourceLocator | 
| 43: | { | 
| 44: | $realInstallationPath = (string) realpath($installationPath); | 
| 45: |  | 
| 46: | if (! is_dir($realInstallationPath)) { | 
| 47: | throw InvalidProjectDirectory::atPath($installationPath); | 
| 48: | } | 
| 49: |  | 
| 50: | $composerJsonPath = $realInstallationPath . '/composer.json'; | 
| 51: |  | 
| 52: | if (! is_file($composerJsonPath)) { | 
| 53: | throw MissingComposerJson::inProjectPath($installationPath); | 
| 54: | } | 
| 55: |  | 
| 56: | $composerJsonContent = file_get_contents($composerJsonPath); | 
| 57: | assert(is_string($composerJsonContent)); | 
| 58: |  | 
| 59: |  | 
| 60: | $composer  = json_decode($composerJsonContent, true, 512, JSON_THROW_ON_ERROR); | 
| 61: | $vendorDir = rtrim($composer['config']['vendor-dir'] ?? 'vendor', '/'); | 
| 62: |  | 
| 63: | $installedJsonPath = $realInstallationPath . '/' . $vendorDir . '/composer/installed.json'; | 
| 64: |  | 
| 65: | if (! is_file($installedJsonPath)) { | 
| 66: | throw MissingInstalledJson::inProjectPath($realInstallationPath . '/' . $vendorDir); | 
| 67: | } | 
| 68: |  | 
| 69: | $jsonContent = file_get_contents($installedJsonPath); | 
| 70: | assert(is_string($jsonContent)); | 
| 71: |  | 
| 72: |  | 
| 73: | $installedJson = json_decode($jsonContent, true, 512, JSON_THROW_ON_ERROR); | 
| 74: |  | 
| 75: |  | 
| 76: | $installed = $installedJson['packages'] ?? $installedJson; | 
| 77: |  | 
| 78: | $classMapPaths       = array_merge( | 
| 79: | $this->prefixPaths($this->packageToClassMapPaths($composer), $realInstallationPath . '/'), | 
| 80: | ...array_map(fn (array $package): array => $this->prefixPaths( | 
| 81: | $this->packageToClassMapPaths($package), | 
| 82: | $this->packagePrefixPath($realInstallationPath, $package, $vendorDir), | 
| 83: | ), $installed), | 
| 84: | ); | 
| 85: | $classMapFiles       = array_filter($classMapPaths, 'is_file'); | 
| 86: | $classMapDirectories = array_values(array_filter($classMapPaths, 'is_dir')); | 
| 87: | $filePaths           = array_merge( | 
| 88: | $this->prefixPaths($this->packageToFilePaths($composer), $realInstallationPath . '/'), | 
| 89: | ...array_map(fn (array $package): array => $this->prefixPaths( | 
| 90: | $this->packageToFilePaths($package), | 
| 91: | $this->packagePrefixPath($realInstallationPath, $package, $vendorDir), | 
| 92: | ), $installed), | 
| 93: | ); | 
| 94: |  | 
| 95: | return new AggregateSourceLocator(array_merge( | 
| 96: | [ | 
| 97: | new PsrAutoloaderLocator( | 
| 98: | Psr4Mapping::fromArrayMappings(array_merge_recursive( | 
| 99: | $this->prefixWithInstallationPath($this->packageToPsr4AutoloadNamespaces($composer), $realInstallationPath), | 
| 100: | ...array_map(fn (array $package): array => $this->prefixWithPackagePath( | 
| 101: | $this->packageToPsr4AutoloadNamespaces($package), | 
| 102: | $realInstallationPath, | 
| 103: | $package, | 
| 104: | $vendorDir, | 
| 105: | ), $installed), | 
| 106: | )), | 
| 107: | $astLocator, | 
| 108: | ), | 
| 109: | new PsrAutoloaderLocator( | 
| 110: | Psr0Mapping::fromArrayMappings(array_merge_recursive( | 
| 111: | $this->prefixWithInstallationPath($this->packageToPsr0AutoloadNamespaces($composer), $realInstallationPath), | 
| 112: | ...array_map(fn (array $package): array => $this->prefixWithPackagePath( | 
| 113: | $this->packageToPsr0AutoloadNamespaces($package), | 
| 114: | $realInstallationPath, | 
| 115: | $package, | 
| 116: | $vendorDir, | 
| 117: | ), $installed), | 
| 118: | )), | 
| 119: | $astLocator, | 
| 120: | ), | 
| 121: | new DirectoriesSourceLocator($classMapDirectories, $astLocator), | 
| 122: | ], | 
| 123: | ...array_map( | 
| 124: | static function (string $file) use ($astLocator): array { | 
| 125: | assert($file !== ''); | 
| 126: |  | 
| 127: | return [new SingleFileSourceLocator($file, $astLocator)]; | 
| 128: | }, | 
| 129: | array_merge($classMapFiles, $filePaths), | 
| 130: | ), | 
| 131: | )); | 
| 132: | } | 
| 133: |  | 
| 134: |  | 
| 135: |  | 
| 136: |  | 
| 137: |  | 
| 138: |  | 
| 139: | private function packageToPsr4AutoloadNamespaces(array $package): array | 
| 140: | { | 
| 141: | return array_map(static fn ($namespacePaths): array => (array) $namespacePaths, $package['autoload']['psr-4'] ?? []); | 
| 142: | } | 
| 143: |  | 
| 144: |  | 
| 145: |  | 
| 146: |  | 
| 147: |  | 
| 148: |  | 
| 149: | private function packageToPsr0AutoloadNamespaces(array $package): array | 
| 150: | { | 
| 151: | return array_map(static fn ($namespacePaths): array => (array) $namespacePaths, $package['autoload']['psr-0'] ?? []); | 
| 152: | } | 
| 153: |  | 
| 154: |  | 
| 155: |  | 
| 156: |  | 
| 157: |  | 
| 158: |  | 
| 159: | private function packageToClassMapPaths(array $package): array | 
| 160: | { | 
| 161: | return $package['autoload']['classmap'] ?? []; | 
| 162: | } | 
| 163: |  | 
| 164: |  | 
| 165: |  | 
| 166: |  | 
| 167: |  | 
| 168: |  | 
| 169: | private function packageToFilePaths(array $package): array | 
| 170: | { | 
| 171: | return $package['autoload']['files'] ?? []; | 
| 172: | } | 
| 173: |  | 
| 174: |  | 
| 175: | private function packagePrefixPath(string $trimmedInstallationPath, array $package, string $vendorDir): string | 
| 176: | { | 
| 177: | return $trimmedInstallationPath . '/' . $vendorDir . '/' . $package['name'] . '/'; | 
| 178: | } | 
| 179: |  | 
| 180: |  | 
| 181: |  | 
| 182: |  | 
| 183: |  | 
| 184: |  | 
| 185: |  | 
| 186: | private function prefixWithPackagePath(array $paths, string $trimmedInstallationPath, array $package, string $vendorDir): array | 
| 187: | { | 
| 188: | $prefix = $this->packagePrefixPath($trimmedInstallationPath, $package, $vendorDir); | 
| 189: |  | 
| 190: | return array_map(fn (array $paths): array => $this->prefixPaths($paths, $prefix), $paths); | 
| 191: | } | 
| 192: |  | 
| 193: |  | 
| 194: |  | 
| 195: |  | 
| 196: |  | 
| 197: |  | 
| 198: | private function prefixWithInstallationPath(array $paths, string $trimmedInstallationPath): array | 
| 199: | { | 
| 200: | return array_map(fn (array $paths): array => $this->prefixPaths($paths, $trimmedInstallationPath . '/'), $paths); | 
| 201: | } | 
| 202: |  | 
| 203: |  | 
| 204: |  | 
| 205: |  | 
| 206: |  | 
| 207: |  | 
| 208: | private function prefixPaths(array $paths, string $prefix): array | 
| 209: | { | 
| 210: | return array_map(static fn (string $path): string => $prefix . $path, $paths); | 
| 211: | } | 
| 212: | } | 
| 213: |  |