Skip to content

Commit 719ec85

Browse files
committed
Add package resolution to resolve namespace / files to class name and vice versa
1 parent e45ef36 commit 719ec85

File tree

5 files changed

+722
-0
lines changed

5 files changed

+722
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"nikic/php-parser": "^4.2"
3030
},
3131
"require-dev": {
32+
"laminas/laminas-filter": "^2.9",
3233
"phpspec/prophecy-phpunit": "^2.0",
3334
"phpstan/phpstan": "^0.12.33",
3435
"phpstan/phpstan-strict-rules": "^0.12.4",

src/Package/ClassInfo.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
/**
4+
* @see https://github.com/open-code-modeling/php-code-ast for the canonical source repository
5+
* @copyright https://github.com/open-code-modeling/php-code-ast/blob/master/COPYRIGHT.md
6+
* @license https://github.com/open-code-modeling/php-code-ast/blob/master/LICENSE.md MIT License
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace OpenCodeModeling\CodeAst\Package;
12+
13+
/**
14+
* Defines methods to know the package, the namespace and the file path for a class.
15+
*/
16+
interface ClassInfo
17+
{
18+
/**
19+
* Returns the package prefix. This is prefixed to every class name.
20+
*
21+
* @return string
22+
*/
23+
public function getPackagePrefix(): string;
24+
25+
/**
26+
* Returns the path to the code directory.
27+
*
28+
* @return string
29+
*/
30+
public function getSourceFolder(): string;
31+
32+
/**
33+
* Class namespace is determined by package prefix, source folder and given path.
34+
*
35+
* @param string $path
36+
* @return string
37+
*/
38+
public function getClassNamespaceFromPath(string $path): string;
39+
40+
/**
41+
* Returns the class name including namespace based on the file name
42+
*
43+
* @param string $filename
44+
* @return string
45+
*/
46+
public function getFullyQualifiedClassNameFromFilename(string $filename): string;
47+
48+
/**
49+
* Returns the class namespace from FQCN.
50+
*
51+
* @param string $fcqn Full qualified class name
52+
* @return string
53+
*/
54+
public function getClassNamespace(string $fcqn): string;
55+
56+
/**
57+
* Extracts class name from FQCN.
58+
*
59+
* @param string $fqcn Full class qualified name
60+
* @return string Class name
61+
*/
62+
public function getClassName(string $fqcn): string;
63+
64+
/**
65+
* Path is extracted from class name by using package prefix and source folder.
66+
*
67+
* @param string $fqcn
68+
* @return string
69+
*/
70+
public function getPath(string $fqcn): string;
71+
72+
/**
73+
* Returns path to file with source folder.
74+
*
75+
* @param string $path Path without source folder
76+
* @param string $name Class name
77+
* @return string
78+
*/
79+
public function getFilenameFromPathAndName(string $path, string $name): string;
80+
81+
/**
82+
* Returns the path and name as a list based on the passed file name.
83+
*
84+
* @param string $filename
85+
* @return array
86+
*/
87+
public function getPathAndNameFromFilename(string $filename): array;
88+
89+
/**
90+
* Checks whether the passed path or file name belongs to this namespace or package.
91+
*
92+
* @param string $filenameOrPath
93+
* @return bool
94+
*/
95+
public function isValidPath(string $filenameOrPath): bool;
96+
}

src/Package/ClassInfoList.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
/**
4+
* @see https://github.com/open-code-modeling/php-code-ast for the canonical source repository
5+
* @copyright https://github.com/open-code-modeling/php-code-ast/blob/master/COPYRIGHT.md
6+
* @license https://github.com/open-code-modeling/php-code-ast/blob/master/LICENSE.md MIT License
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace OpenCodeModeling\CodeAst\Package;
12+
13+
use OpenCodeModeling\CodeAst\Exception\RuntimeException;
14+
15+
/**
16+
* Stores a list of objects of type \OpenCodeModeling\CodeGenerator\Code\ClassInfo
17+
*/
18+
final class ClassInfoList
19+
{
20+
/**
21+
* @var ClassInfo[]
22+
**/
23+
private $list = [];
24+
25+
public function __construct(ClassInfo ...$classInfo)
26+
{
27+
$this->addClassInfo(...$classInfo);
28+
}
29+
30+
/**
31+
* Adds more ClassInfo instances to the list
32+
*
33+
* @param ClassInfo ...$classInfo
34+
*/
35+
public function addClassInfo(ClassInfo ...$classInfo): void
36+
{
37+
foreach ($classInfo as $item) {
38+
$this->list[$item->getPackagePrefix()] = $item;
39+
}
40+
}
41+
42+
/**
43+
* Returns the appropriate ClassInfo object based on the provided path.
44+
*
45+
* @param string $path Path for which ClassInfo instance should be retrieved
46+
* @return ClassInfo
47+
*/
48+
public function classInfoForPath(string $path): ClassInfo
49+
{
50+
foreach ($this->list as $classInfo) {
51+
if (0 === \strpos($path, $classInfo->getSourceFolder())) {
52+
return $classInfo;
53+
}
54+
}
55+
throw new RuntimeException(
56+
\sprintf('No class info found for path "%s". Check your namespace configuration.', $path)
57+
);
58+
}
59+
60+
/**
61+
* Returns the appropriate ClassInfo object based on the provided filename.
62+
*
63+
* @param string $filename Filename for which ClassInfo instance should be retrieved
64+
* @return ClassInfo
65+
*/
66+
public function classInfoForFilename(string $filename): ClassInfo
67+
{
68+
foreach ($this->list as $classInfo) {
69+
if ($classInfo->isValidPath($filename)) {
70+
return $classInfo;
71+
}
72+
}
73+
throw new RuntimeException(
74+
\sprintf('No class info found for filename "%s". Check your namespace configuration.', $filename)
75+
);
76+
}
77+
}

src/Package/Psr4Info.php

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
<?php
2+
3+
/**
4+
* @see https://github.com/open-code-modeling/php-code-ast for the canonical source repository
5+
* @copyright https://github.com/open-code-modeling/php-code-ast/blob/master/COPYRIGHT.md
6+
* @license https://github.com/open-code-modeling/php-code-ast/blob/master/LICENSE.md MIT License
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace OpenCodeModeling\CodeAst\Package;
12+
13+
use Composer\Autoload\ClassLoader;
14+
15+
final class Psr4Info implements ClassInfo
16+
{
17+
/**
18+
* source folder
19+
*
20+
* @var string
21+
*/
22+
private $sourceFolder;
23+
24+
/**
25+
* Package prefix
26+
*
27+
* @var string
28+
*/
29+
private $packagePrefix;
30+
31+
/**
32+
* @var callable
33+
*/
34+
private $filterDirectoryToNamespace;
35+
36+
/**
37+
* @var callable
38+
*/
39+
protected $filterNamespaceToDirectory;
40+
41+
/**
42+
* Configure PSR-4 meta info
43+
*
44+
* @param string $sourceFolder Absolute path to the source folder
45+
* @param string $packagePrefix Package prefix which is used as class namespace
46+
* @param callable $filterDirectoryToNamespace Callable to filter a directory to a namespace
47+
* @param callable $filterNamespaceToDirectory Callable to filter a namespace to a directory
48+
*/
49+
public function __construct(
50+
string $sourceFolder,
51+
string $packagePrefix,
52+
callable $filterDirectoryToNamespace,
53+
callable $filterNamespaceToDirectory
54+
) {
55+
$this->sourceFolder = \rtrim($sourceFolder, '/');
56+
$this->packagePrefix = \trim($packagePrefix, '\\');
57+
$this->filterDirectoryToNamespace = $filterDirectoryToNamespace;
58+
$this->filterNamespaceToDirectory = $filterNamespaceToDirectory;
59+
}
60+
61+
public function getPackagePrefix(): string
62+
{
63+
return $this->packagePrefix;
64+
}
65+
66+
/**
67+
* Class namespace is determined by package prefix, source folder and given path.
68+
*
69+
* @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md#3-examples
70+
*
71+
* @param string $path
72+
* @return string
73+
*/
74+
public function getClassNamespaceFromPath(string $path): string
75+
{
76+
$namespace = ($this->filterDirectoryToNamespace)($this->normalizePath($path));
77+
78+
return $this->normalizeNamespace($this->getPackagePrefix() . '\\' . $namespace);
79+
}
80+
81+
public function getFullyQualifiedClassNameFromFilename(string $filename): string
82+
{
83+
[$path, $name] = $this->getPathAndNameFromFilename($filename);
84+
85+
return $this->getClassNamespaceFromPath($path) . '\\' . $name;
86+
}
87+
88+
public function getClassNamespace(string $fcqn): string
89+
{
90+
$namespace = $this->normalizeNamespace($fcqn);
91+
$namespace = \substr($namespace, 0, \strrpos($namespace, '/'));
92+
93+
return $this->normalizeNamespace($this->getPackagePrefix() . '\\' . $namespace);
94+
}
95+
96+
public function getClassName(string $fqcn): string
97+
{
98+
$fqcn = $this->normalizeNamespace($fqcn);
99+
100+
return \trim(\substr($fqcn, \strrpos($fqcn, '\\')), '\\');
101+
}
102+
103+
public function getPath(string $fqcn): string
104+
{
105+
$fqcn = $this->normalizeNamespace($fqcn);
106+
$namespace = \str_replace($this->getPackagePrefix(), '', $fqcn);
107+
$namespace = \ltrim(\substr($namespace, 0, \strrpos($namespace, '\\')), '\\');
108+
109+
return ($this->filterNamespaceToDirectory)($namespace);
110+
}
111+
112+
public function getFilenameFromPathAndName(string $path, string $name): string
113+
{
114+
$filePath = $this->getSourceFolder() . DIRECTORY_SEPARATOR;
115+
116+
if ($path = \trim($path, '/')) {
117+
$filePath .= $this->normalizePath($path) . DIRECTORY_SEPARATOR;
118+
}
119+
120+
return $filePath . $name . '.php';
121+
}
122+
123+
public function getPathAndNameFromFilename(string $filename): array
124+
{
125+
$path = \substr($filename, 0, \strrpos($filename, DIRECTORY_SEPARATOR));
126+
$name = \substr($filename, \strrpos($filename, DIRECTORY_SEPARATOR) + 1);
127+
$name = \substr($name, 0, \strpos($name, '.') ?: \strlen($name));
128+
129+
return [$this->normalizePath($path), $name];
130+
}
131+
132+
public function isValidPath(string $filenameOrPath): bool
133+
{
134+
$path = \substr($filenameOrPath, 0, \strrpos($filenameOrPath, DIRECTORY_SEPARATOR));
135+
136+
if (0 === \strpos($path, $this->sourceFolder)) {
137+
return true;
138+
}
139+
140+
return false;
141+
}
142+
143+
public function getSourceFolder(): string
144+
{
145+
return $this->sourceFolder;
146+
}
147+
148+
/**
149+
* Removes duplicates of backslashes and trims backslashes
150+
*
151+
* @param string $namespace
152+
* @return string
153+
*/
154+
private function normalizeNamespace(string $namespace): string
155+
{
156+
$namespace = \str_replace('\\\\', '\\', $namespace);
157+
158+
return \trim($namespace, '\\');
159+
}
160+
161+
/**
162+
* Remove source folder from path
163+
*
164+
* @param string $path
165+
* @return string
166+
*/
167+
private function normalizePath(string $path): string
168+
{
169+
return \preg_replace('/^' . \addcslashes($this->sourceFolder, '/') . '\//', '', $path);
170+
}
171+
172+
/**
173+
* Creates an instance of the class Psr4Info based on the Composer configuration.
174+
*
175+
* @param ClassLoader $classLoader Composer ClassLoader instance
176+
* @param callable $filterDirectoryToNamespace Callable to filter a directory to a namespace
177+
* @param callable $filterNamespaceToDirectory Callable to filter a namespace to a directory
178+
* @param string $exclude Specifies which path should be ignored
179+
* @return array
180+
*/
181+
public static function fromComposer(
182+
ClassLoader $classLoader,
183+
callable $filterDirectoryToNamespace,
184+
callable $filterNamespaceToDirectory,
185+
string $exclude = 'vendor' . DIRECTORY_SEPARATOR
186+
): array {
187+
$namespaces = [];
188+
189+
foreach ($classLoader->getPrefixesPsr4() as $namespace => $paths) {
190+
$realpath = \preg_replace('/^' . \addcslashes(\getcwd(), '/') . '\//', '', \realpath($paths[0]));
191+
if (false !== \stripos($realpath, $exclude)) {
192+
continue;
193+
}
194+
$classInfo = new self($realpath, $namespace, $filterDirectoryToNamespace, $filterNamespaceToDirectory);
195+
196+
$namespaces[] = $classInfo;
197+
}
198+
199+
return $namespaces;
200+
}
201+
}

0 commit comments

Comments
 (0)