1 | <?php |
||
32 | class Resolver |
||
33 | { |
||
34 | /** |
||
35 | * Instance of composer, since this will be used to load the ps4 prefixes |
||
36 | * |
||
37 | * @var ClassLoader |
||
38 | */ |
||
39 | private $composer; |
||
40 | |||
41 | /** |
||
42 | * @var Psr4Namespace |
||
43 | */ |
||
44 | private $namespace; |
||
45 | |||
46 | /** |
||
47 | * Resolver constructor. |
||
48 | * |
||
49 | * @param Psr4Namespace|string $namespace |
||
50 | * @param ClassLoader $composer |
||
51 | */ |
||
52 | 30 | public function __construct($namespace, ClassLoader $composer) |
|
53 | { |
||
54 | 30 | $this->setNamespace($namespace); |
|
55 | 30 | $this->composer = $composer; |
|
56 | 30 | } |
|
57 | |||
58 | /** |
||
59 | * Set the namespace to resolve |
||
60 | * |
||
61 | * @param Psr4Namespace|string $namespace $namespace |
||
62 | */ |
||
63 | 30 | public function setNamespace($namespace) |
|
64 | { |
||
65 | 30 | if (!($namespace instanceof Psr4Namespace)) { |
|
66 | 30 | $namespace = new Psr4Namespace($namespace); |
|
67 | } |
||
68 | 30 | $this->namespace = $namespace; |
|
69 | 30 | } |
|
70 | |||
71 | /** |
||
72 | * Get the current namespace |
||
73 | * |
||
74 | * @return Psr4Namespace |
||
75 | */ |
||
76 | 6 | public function getNamespace(): Psr4Namespace |
|
77 | { |
||
78 | 6 | return $this->namespace; |
|
79 | } |
||
80 | |||
81 | /** |
||
82 | * Find all of the available constructs under a specific namespace |
||
83 | * |
||
84 | * @param string $instanceOf optional, restrict the classes found to those |
||
85 | * that extend from this base |
||
86 | * @return array a list of FQCN's that match |
||
87 | */ |
||
88 | 6 | public function findConstructs(string $instanceOf = null): array |
|
89 | { |
||
90 | 6 | $availablePaths = $this->findDirectories(); |
|
91 | |||
92 | 6 | $constructs = $this->findNamespacedConstuctsInDirectories($availablePaths, $this->namespace); |
|
93 | |||
94 | // apply filtering |
||
95 | 6 | if ($instanceOf !== null) { |
|
96 | 3 | $constructs = array_values(array_filter($constructs, function ($constructName) use ($instanceOf) { |
|
97 | 3 | return is_subclass_of($constructName, $instanceOf); |
|
98 | 3 | })); |
|
99 | } |
||
100 | |||
101 | 6 | return $constructs; |
|
102 | } |
||
103 | |||
104 | /** |
||
105 | * Resolve a psr4 based namespace to a list of absolute directory paths |
||
106 | * |
||
107 | * @return array list of directories this namespace is mapped to |
||
108 | * @throws Exception |
||
109 | */ |
||
110 | 15 | public function findDirectories(): array |
|
111 | { |
||
112 | 15 | $prefixes = $this->composer->getPrefixesPsr4(); |
|
113 | // pluck the best namespace from the available |
||
114 | 15 | $namespacePrefix = $this->findNamespacePrefix($this->namespace, array_keys($prefixes)); |
|
115 | 15 | if (!$namespacePrefix) { |
|
116 | 3 | throw new Exception('Could not find registered psr4 prefix that matches '.$this->namespace); |
|
117 | } |
||
118 | |||
119 | 12 | return $this->buildDirectoryList($prefixes[$namespacePrefix->getValue()], $this->namespace, $namespacePrefix); |
|
120 | } |
||
121 | |||
122 | /** |
||
123 | * Build a list of absolute paths, for the given namespace, based on the relative $prefix |
||
124 | * |
||
125 | * @param array $directories the list of directories (their position relates to $prefix) |
||
126 | * @param Psr4Namespace $namespace The base namespace |
||
127 | * @param Psr4Namespace $prefix The psr4 namespace related to the list of provided directories |
||
128 | * @return array directory paths for provided namespace |
||
129 | */ |
||
130 | 12 | private function buildDirectoryList(array $directories, Psr4Namespace $namespace, Psr4Namespace $prefix): array |
|
131 | { |
||
132 | 12 | $discovered = []; |
|
133 | 12 | foreach ($directories as $path) { |
|
134 | 12 | $path = (new PathBuilder($path, $prefix))->resolve($namespace); |
|
135 | // convert the rest of the relative path, from the prefix into a directory slug |
||
136 | 12 | if ($path && is_dir($path)) { |
|
137 | 10 | $discovered[] = $path; |
|
138 | } |
||
139 | } |
||
140 | 12 | return $discovered; |
|
141 | } |
||
142 | |||
143 | /** |
||
144 | * Find the best psr4 namespace prefix, based on the supplied namespace, and |
||
145 | * list of provided prefix |
||
146 | * |
||
147 | * @param Psr4Namespace $namespace |
||
148 | * @param array $namespacePrefixes |
||
149 | * @return Psr4Namespace |
||
150 | */ |
||
151 | 15 | private function findNamespacePrefix(Psr4Namespace $namespace, array $namespacePrefixes) |
|
152 | { |
||
153 | 15 | $prefixResult = null; |
|
154 | |||
155 | // find the best matching prefix! |
||
156 | 15 | foreach ($namespacePrefixes as $prefix) { |
|
157 | 15 | $prefix = new Psr4Namespace($prefix); |
|
158 | 15 | if ($namespace->startsWith($prefix) && |
|
159 | 15 | ($prefixResult === null || $prefix->length() > $prefixResult->length()) |
|
160 | ) { |
||
161 | // if we have a match, and it's longer than the previous match |
||
162 | 13 | $prefixResult = $prefix; |
|
163 | } |
||
164 | } |
||
165 | 15 | return $prefixResult; |
|
166 | } |
||
167 | |||
168 | /** |
||
169 | * Retrieve a directory iterator for the supplied path |
||
170 | * |
||
171 | * @param string $path The directory to iterate |
||
172 | * @return RegexIterator |
||
173 | */ |
||
174 | 6 | private function getDirectoryIterator(string $path): RegexIterator |
|
175 | { |
||
176 | 6 | $dirIterator = new RecursiveDirectoryIterator($path); |
|
177 | 6 | $iterator = new RecursiveIteratorIterator($dirIterator); |
|
178 | 6 | return new RegexIterator($iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH); |
|
179 | } |
||
180 | |||
181 | /** |
||
182 | * Determine if the construct (class, interface or trait) exists |
||
183 | * |
||
184 | * @param string $constructName |
||
185 | * @return bool |
||
186 | */ |
||
187 | 6 | private function languageConstructExists(string $constructName): bool |
|
193 | |||
194 | /** |
||
195 | * Determine if the construct exists |
||
196 | * |
||
197 | * @param string $constructName |
||
198 | * @param bool $autoload trigger the autoloader to be fired, if the construct |
||
199 | * doesn't exist |
||
200 | * @return bool |
||
201 | */ |
||
202 | 6 | private function checkConstructExists(string $constructName, bool $autoload = true): bool |
|
209 | |||
210 | /** |
||
211 | * Process a list of directories, searching for language constructs (classes, |
||
212 | * interfaces, traits) that exist in them, based on the supplied base |
||
213 | * namespace |
||
214 | * |
||
215 | * @param array $directories list of absolute directory paths |
||
216 | * @param Psr4Namespace $namespace The namespace these directories are representing |
||
217 | * @return array |
||
218 | */ |
||
219 | 6 | private function findNamespacedConstuctsInDirectories(array $directories, Psr4Namespace $namespace): array |
|
230 | |||
231 | /** |
||
232 | * Recurisvely scan the supplied directory for language constructs that are |
||
233 | * $namespaced |
||
234 | * |
||
235 | * @param string $directory The directory to scan |
||
236 | * @param Psr4Namespace $namespace the namespace that represents this directory |
||
237 | * @return array |
||
238 | */ |
||
239 | 6 | private function findNamespacedConstuctsInDirectory(string $directory, Psr4Namespace $namespace): array |
|
252 | } |
||
253 |