|   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\FailedToParseJson; | 
|  10:  | use PHPStan\BetterReflection\SourceLocator\Type\Composer\Factory\Exception\InvalidProjectDirectory; | 
|  11:  | use PHPStan\BetterReflection\SourceLocator\Type\Composer\Factory\Exception\MissingComposerJson; | 
|  12:  | use PHPStan\BetterReflection\SourceLocator\Type\Composer\Factory\Exception\MissingInstalledJson; | 
|  13:  | use PHPStan\BetterReflection\SourceLocator\Type\Composer\Psr\Psr0Mapping; | 
|  14:  | use PHPStan\BetterReflection\SourceLocator\Type\Composer\Psr\Psr4Mapping; | 
|  15:  | use PHPStan\BetterReflection\SourceLocator\Type\Composer\PsrAutoloaderLocator; | 
|  16:  | use PHPStan\BetterReflection\SourceLocator\Type\DirectoriesSourceLocator; | 
|  17:  | use PHPStan\BetterReflection\SourceLocator\Type\SingleFileSourceLocator; | 
|  18:  | use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; | 
|  19:  |  | 
|  20:  | use function array_filter; | 
|  21:  | use function array_map; | 
|  22:  | use function array_merge; | 
|  23:  | use function array_merge_recursive; | 
|  24:  | use function array_values; | 
|  25:  | use function file_get_contents; | 
|  26:  | use function is_array; | 
|  27:  | use function is_dir; | 
|  28:  | use function is_file; | 
|  29:  | use function json_decode; | 
|  30:  | use function realpath; | 
|  31:  | use function rtrim; | 
|  32:  |  | 
|  33:  |  | 
|  34:  | final class MakeLocatorForInstalledJson | 
|  35:  | { | 
|  36:  |     public function __invoke(string $installationPath, Locator $astLocator): SourceLocator | 
|  37:  |     { | 
|  38:  |         $realInstallationPath = (string) realpath($installationPath); | 
|  39:  |  | 
|  40:  |         if (! is_dir($realInstallationPath)) { | 
|  41:  |             throw InvalidProjectDirectory::atPath($installationPath); | 
|  42:  |         } | 
|  43:  |  | 
|  44:  |         $composerJsonPath = $realInstallationPath . '/composer.json'; | 
|  45:  |  | 
|  46:  |         if (! is_file($composerJsonPath)) { | 
|  47:  |             throw MissingComposerJson::inProjectPath($installationPath); | 
|  48:  |         } | 
|  49:  |  | 
|  50:  |          | 
|  51:  |         $composer  = json_decode((string) file_get_contents($composerJsonPath), true); | 
|  52:  |         $vendorDir = $composer['config']['vendor-dir'] ?? 'vendor'; | 
|  53:  |         $vendorDir = rtrim($vendorDir, '/'); | 
|  54:  |  | 
|  55:  |         $installedJsonPath = $realInstallationPath . '/' . $vendorDir . '/composer/installed.json'; | 
|  56:  |  | 
|  57:  |         if (! is_file($installedJsonPath)) { | 
|  58:  |             throw MissingInstalledJson::inProjectPath($realInstallationPath . '/' . $vendorDir); | 
|  59:  |         } | 
|  60:  |  | 
|  61:  |          | 
|  62:  |         $installedJson = json_decode((string) file_get_contents($installedJsonPath), true); | 
|  63:  |  | 
|  64:  |         if (! is_array($installedJson)) { | 
|  65:  |             throw FailedToParseJson::inFile($installedJsonPath); | 
|  66:  |         } | 
|  67:  |  | 
|  68:  |          | 
|  69:  |         $installed = $installedJson['packages'] ?? $installedJson; | 
|  70:  |  | 
|  71:  |         $classMapPaths       = array_merge([], ...array_map(function (array $package) use ($realInstallationPath, $vendorDir) : array { | 
|  72:  |             return $this->prefixPaths($this->packageToClassMapPaths($package), $this->packagePrefixPath($realInstallationPath, $package, $vendorDir)); | 
|  73:  |         }, $installed)); | 
|  74:  |         $classMapFiles       = array_filter($classMapPaths, 'is_file'); | 
|  75:  |         $classMapDirectories = array_values(array_filter($classMapPaths, 'is_dir')); | 
|  76:  |         $filePaths           = array_merge([], ...array_map(function (array $package) use ($realInstallationPath, $vendorDir) : array { | 
|  77:  |             return $this->prefixPaths($this->packageToFilePaths($package), $this->packagePrefixPath($realInstallationPath, $package, $vendorDir)); | 
|  78:  |         }, $installed)); | 
|  79:  |  | 
|  80:  |         return new AggregateSourceLocator(array_merge([ | 
|  81:  |             new PsrAutoloaderLocator(Psr4Mapping::fromArrayMappings(array_merge_recursive([], ...array_map(function (array $package) use ($realInstallationPath, $vendorDir) : array { | 
|  82:  |                 return $this->prefixWithPackagePath($this->packageToPsr4AutoloadNamespaces($package), $realInstallationPath, $package, $vendorDir); | 
|  83:  |             }, $installed))), $astLocator), | 
|  84:  |             new PsrAutoloaderLocator(Psr0Mapping::fromArrayMappings(array_merge_recursive([], ...array_map(function (array $package) use ($realInstallationPath, $vendorDir) : array { | 
|  85:  |                 return $this->prefixWithPackagePath($this->packageToPsr0AutoloadNamespaces($package), $realInstallationPath, $package, $vendorDir); | 
|  86:  |             }, $installed))), $astLocator), | 
|  87:  |             new DirectoriesSourceLocator($classMapDirectories, $astLocator), | 
|  88:  |         ], ...array_map(static function (string $file) use ($astLocator) : array { | 
|  89:  |             return [new SingleFileSourceLocator($file, $astLocator)]; | 
|  90:  |         }, array_merge($classMapFiles, $filePaths)))); | 
|  91:  |     } | 
|  92:  |  | 
|  93:  |      | 
|  94:  |  | 
|  95:  |  | 
|  96:  |  | 
|  97:  |  | 
|  98:  |     private function packageToPsr4AutoloadNamespaces(array $package): array | 
|  99:  |     { | 
| 100:  |         return array_map(static function ($namespacePaths) : array { | 
| 101:  |             return (array) $namespacePaths; | 
| 102:  |         }, $package['autoload']['psr-4'] ?? []); | 
| 103:  |     } | 
| 104:  |  | 
| 105:  |      | 
| 106:  |  | 
| 107:  |  | 
| 108:  |  | 
| 109:  |  | 
| 110:  |     private function packageToPsr0AutoloadNamespaces(array $package): array | 
| 111:  |     { | 
| 112:  |         return array_map(static function ($namespacePaths) : array { | 
| 113:  |             return (array) $namespacePaths; | 
| 114:  |         }, $package['autoload']['psr-0'] ?? []); | 
| 115:  |     } | 
| 116:  |  | 
| 117:  |      | 
| 118:  |  | 
| 119:  |  | 
| 120:  |  | 
| 121:  |  | 
| 122:  |     private function packageToClassMapPaths(array $package): array | 
| 123:  |     { | 
| 124:  |         return $package['autoload']['classmap'] ?? []; | 
| 125:  |     } | 
| 126:  |  | 
| 127:  |      | 
| 128:  |  | 
| 129:  |  | 
| 130:  |  | 
| 131:  |  | 
| 132:  |     private function packageToFilePaths(array $package): array | 
| 133:  |     { | 
| 134:  |         return $package['autoload']['files'] ?? []; | 
| 135:  |     } | 
| 136:  |  | 
| 137:  |      | 
| 138:  |     private function packagePrefixPath(string $trimmedInstallationPath, array $package, string $vendorDir): string | 
| 139:  |     { | 
| 140:  |         return $trimmedInstallationPath . '/' . $vendorDir . '/' . $package['name'] . '/'; | 
| 141:  |     } | 
| 142:  |  | 
| 143:  |      | 
| 144:  |  | 
| 145:  |  | 
| 146:  |  | 
| 147:  |  | 
| 148:  |  | 
| 149:  |     private function prefixWithPackagePath(array $paths, string $trimmedInstallationPath, array $package, string $vendorDir): array | 
| 150:  |     { | 
| 151:  |         $prefix = $this->packagePrefixPath($trimmedInstallationPath, $package, $vendorDir); | 
| 152:  |  | 
| 153:  |         return array_map(function (array $paths) use ($prefix) : array { | 
| 154:  |             return $this->prefixPaths($paths, $prefix); | 
| 155:  |         }, $paths); | 
| 156:  |     } | 
| 157:  |  | 
| 158:  |      | 
| 159:  |  | 
| 160:  |  | 
| 161:  |  | 
| 162:  |  | 
| 163:  |     private function prefixPaths(array $paths, string $prefix): array | 
| 164:  |     { | 
| 165:  |         return array_map(static function (string $path) use ($prefix) : string { | 
| 166:  |             return $prefix . $path; | 
| 167:  |         }, $paths); | 
| 168:  |     } | 
| 169:  | } | 
| 170:  |  |