1: <?php declare(strict_types = 1);
2:
3: namespace ApiGen;
4:
5: use ApiGen\Info\ClassLikeReferenceInfo;
6: use Composer\Autoload\ClassLoader;
7: use JetBrains\PHPStormStub\PhpStormStubsMap;
8: use League;
9: use Nette\Utils\FileSystem;
10: use Nette\Utils\Json;
11: use PHPStan\Php8StubsMap;
12: use Symfony\Component\Console\Style\OutputStyle;
13:
14: use function dirname;
15: use function implode;
16: use function is_dir;
17: use function is_file;
18: use function strtolower;
19:
20: use const PHP_VERSION_ID;
21:
22:
23: class Locator
24: {
25: /**
26: * @param string[] $stubsMap indexed by [classLikeName]
27: */
28: public function __construct(
29: protected array $stubsMap,
30: protected ClassLoader $classLoader,
31: ) {
32: }
33:
34:
35: public static function create(OutputStyle $output, string $projectDir): self
36: {
37: return new self(
38: self::createStubsMap(),
39: self::createComposerClassLoader($output, $projectDir),
40: );
41: }
42:
43:
44: /**
45: * @return string[] indexed by [classLikeName]
46: */
47: protected static function createStubsMap(): array
48: {
49: $stubsMap = [];
50:
51: $phpStormStubsDir = dirname(Helpers::classLikePath(PhpStormStubsMap::class));
52: foreach (PhpStormStubsMap::CLASSES as $class => $path) {
53: $stubsMap[strtolower($class)] = "$phpStormStubsDir/$path";
54: }
55:
56: $phpStanStubsDir = dirname(Helpers::classLikePath(Php8StubsMap::class));
57: foreach ((new Php8StubsMap(PHP_VERSION_ID))->classes as $class => $path) {
58: $stubsMap[$class] = "$phpStanStubsDir/$path";
59: }
60:
61: return $stubsMap;
62: }
63:
64:
65: protected static function createComposerClassLoader(OutputStyle $output, string $projectDir): ClassLoader
66: {
67: $loader = new ClassLoader();
68: $composerJsonPath = "$projectDir/composer.json";
69:
70: if (!is_file($composerJsonPath)) {
71: $output->warning(implode("\n", [
72: "Unable to use Composer autoloader for finding dependencies because file",
73: "$composerJsonPath does not exist. Use --working-dir to specify directory where composer.json is located",
74: ]));
75:
76: return $loader;
77: }
78:
79: $composerJson = Json::decode(FileSystem::read($composerJsonPath), forceArrays: true);
80: $vendorDir = FileSystem::joinPaths($projectDir, $composerJson['config']['vendor-dir'] ?? 'vendor');
81:
82: if (!is_dir($vendorDir)) {
83: $output->warning(implode("\n", [
84: "Unable to use Composer autoloader for finding dependencies because directory",
85: "$vendorDir does not exist. Run composer install to install dependencies.",
86: ]));
87:
88: return $loader;
89: }
90:
91: $output->text("Using Composer autoloader for finding dependencies in $vendorDir.\n");
92: $loader->addClassMap(require "$vendorDir/composer/autoload_classmap.php");
93:
94: foreach (require "$vendorDir/composer/autoload_namespaces.php" as $prefix => $paths) {
95: $loader->set($prefix, $paths);
96: }
97:
98: foreach (require "$vendorDir/composer/autoload_psr4.php" as $prefix => $paths) {
99: $loader->setPsr4($prefix, $paths);
100: }
101:
102: return $loader;
103: }
104:
105:
106: public function locate(ClassLikeReferenceInfo $name): ?string
107: {
108: return $this->classLoader->findFile($name->full) ?: $this->stubsMap[$name->fullLower] ?? null;
109: }
110: }
111: