Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 76 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
Aliases | |
0.00% |
0 / 76 |
|
0.00% |
0 / 9 |
600 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getTemplate | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
6 | |||
writeAliasesFileForSymbols | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getAliasFilepath | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getModifiedSymbols | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
registerAutoloader | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
buildStringOfAliases | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
getAliasesArray | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
getFunctionAliasesString | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
42 |
1 | <?php |
2 | /** |
3 | * When replacements are made in-situ in the vendor directory, add aliases for the original class fqdns so |
4 | * dev dependencies can still be used. |
5 | * |
6 | * We could make the replacements in the dev dependencies but it is preferable not to edit files unnecessarily. |
7 | * Composer would warn of changes before updating (although it should probably do that already). |
8 | * This approach allows symlinked dev dependencies to be used. |
9 | * It also should work without knowing anything about the dev dependencies |
10 | * |
11 | * @package brianhenryie/strauss |
12 | */ |
13 | |
14 | namespace BrianHenryIE\Strauss\Pipeline; |
15 | |
16 | use BrianHenryIE\Strauss\Config\AliasesConfigInterface; |
17 | use BrianHenryIE\Strauss\Files\File; |
18 | use BrianHenryIE\Strauss\Helpers\FileSystem; |
19 | use BrianHenryIE\Strauss\Helpers\NamespaceSort; |
20 | use BrianHenryIE\Strauss\Types\AutoloadAliasInterface; |
21 | use BrianHenryIE\Strauss\Types\ClassSymbol; |
22 | use BrianHenryIE\Strauss\Types\ConstantSymbol; |
23 | use BrianHenryIE\Strauss\Types\DiscoveredSymbol; |
24 | use BrianHenryIE\Strauss\Types\DiscoveredSymbols; |
25 | use BrianHenryIE\Strauss\Types\FunctionSymbol; |
26 | use BrianHenryIE\Strauss\Types\NamespaceSymbol; |
27 | use Composer\ClassMapGenerator\ClassMapGenerator; |
28 | use League\Flysystem\StorageAttributes; |
29 | use Psr\Log\LoggerAwareTrait; |
30 | use Psr\Log\LoggerInterface; |
31 | use Psr\Log\NullLogger; |
32 | use ReflectionClass; |
33 | |
34 | class Aliases |
35 | { |
36 | use LoggerAwareTrait; |
37 | |
38 | protected AliasesConfigInterface $config; |
39 | |
40 | protected FileSystem $fileSystem; |
41 | |
42 | public function __construct( |
43 | AliasesConfigInterface $config, |
44 | FileSystem $fileSystem, |
45 | ?LoggerInterface $logger = null |
46 | ) { |
47 | $this->config = $config; |
48 | $this->fileSystem = $fileSystem; |
49 | $this->setLogger($logger ?? new NullLogger()); |
50 | } |
51 | |
52 | protected function getTemplate(array $aliasesArray, ?string $autoloadAliasesFunctionsString): string |
53 | { |
54 | $namespace = $this->config->getNamespacePrefix(); |
55 | $autoloadAliases = var_export($aliasesArray, true); |
56 | |
57 | $globalFunctionsString = !$autoloadAliasesFunctionsString ? '' |
58 | : <<<GLOBAL |
59 | // Global functions |
60 | namespace { |
61 | $autoloadAliasesFunctionsString |
62 | } |
63 | GLOBAL; |
64 | |
65 | return <<<TEMPLATE |
66 | <?php |
67 | |
68 | $globalFunctionsString |
69 | |
70 | // Everything else – irrelevant that this part is namespaced |
71 | namespace $namespace { |
72 | |
73 | class AliasAutoloader |
74 | { |
75 | private string \$includeFilePath; |
76 | |
77 | private array \$autoloadAliases = $autoloadAliases; |
78 | |
79 | public function __construct() { |
80 | \$this->includeFilePath = __DIR__ . '/autoload_alias.php'; |
81 | } |
82 | |
83 | public function autoload(\$class) |
84 | { |
85 | if (!isset(\$this->autoloadAliases[\$class])) { |
86 | return; |
87 | } |
88 | switch (\$this->autoloadAliases[\$class]['type']) { |
89 | case 'class': |
90 | \$this->load( |
91 | \$this->classTemplate( |
92 | \$this->autoloadAliases[\$class] |
93 | ) |
94 | ); |
95 | break; |
96 | case 'interface': |
97 | \$this->load( |
98 | \$this->interfaceTemplate( |
99 | \$this->autoloadAliases[\$class] |
100 | ) |
101 | ); |
102 | break; |
103 | case 'trait': |
104 | \$this->load( |
105 | \$this->traitTemplate( |
106 | \$this->autoloadAliases[\$class] |
107 | ) |
108 | ); |
109 | break; |
110 | default: |
111 | // Never. |
112 | break; |
113 | } |
114 | } |
115 | |
116 | private function load(string \$includeFile) |
117 | { |
118 | file_put_contents(\$this->includeFilePath, \$includeFile); |
119 | include \$this->includeFilePath; |
120 | file_exists(\$this->includeFilePath) && unlink(\$this->includeFilePath); |
121 | } |
122 | |
123 | // TODO: What if this was a real function in this class that could be used for testing, which would be read and written by php-parser? |
124 | private function classTemplate(array \$class): string |
125 | { |
126 | \$abstract = \$class['isabstract'] ? 'abstract ' : ''; |
127 | \$classname = \$class['classname']; |
128 | if(isset(\$class['namespace'])) { |
129 | \$namespace = "namespace {\$class['namespace']};"; |
130 | \$extends = '\\\\' . \$class['extends']; |
131 | \$implements = empty(\$class['implements']) ? '' |
132 | : ' implements \\\\' . implode(', \\\\', \$class['implements']); |
133 | } else { |
134 | \$namespace = ''; |
135 | \$extends = \$class['extends']; |
136 | \$implements = !empty(\$class['implements']) ? '' |
137 | : ' implements ' . implode(', ', \$class['implements']); |
138 | } |
139 | return <<<EOD |
140 | <?php |
141 | \$namespace |
142 | \$abstract class \$classname extends \$extends \$implements {} |
143 | EOD; |
144 | } |
145 | |
146 | private function interfaceTemplate(array \$interface): string |
147 | { |
148 | \$interfacename = \$interface['interfacename']; |
149 | \$namespace = isset(\$interface['namespace']) |
150 | ? "namespace {\$interface['namespace']};" : ''; |
151 | \$extends = isset(\$interface['namespace']) |
152 | ? '\\\\' . implode('\\\\ ,', \$interface['extends']) |
153 | : implode(', ', \$interface['extends']); |
154 | return <<<EOD |
155 | <?php |
156 | \$namespace |
157 | interface \$interfacename extends \$extends {} |
158 | EOD; |
159 | } |
160 | private function traitTemplate(array \$trait): string |
161 | { |
162 | \$traitname = \$trait['traitname']; |
163 | \$namespace = isset(\$trait['namespace']) |
164 | ? "namespace {\$trait['namespace']};" : ''; |
165 | \$uses = isset(\$trait['namespace']) |
166 | ? '\\\\' . implode(';' . PHP_EOL . ' use \\\\', \$trait['use']) |
167 | : implode(';' . PHP_EOL . ' use ', \$trait['use']); |
168 | return <<<EOD |
169 | <?php |
170 | \$namespace |
171 | trait \$traitname { |
172 | use \$uses; |
173 | } |
174 | EOD; |
175 | } |
176 | } |
177 | |
178 | spl_autoload_register( [ new AliasAutoloader(), 'autoload' ] ); |
179 | |
180 | } |
181 | TEMPLATE; |
182 | } |
183 | |
184 | public function writeAliasesFileForSymbols(DiscoveredSymbols $symbols): void |
185 | { |
186 | $modifiedSymbols = $this->getModifiedSymbols($symbols); |
187 | |
188 | $outputFilepath = $this->getAliasFilepath(); |
189 | |
190 | $fileString = $this->buildStringOfAliases($modifiedSymbols, basename($outputFilepath)); |
191 | |
192 | $this->fileSystem->write($outputFilepath, $fileString); |
193 | } |
194 | |
195 | /** |
196 | * We will create `vendor/composer/autoload_aliases.php` alongside other autoload files, e.g. `autoload_real.php`. |
197 | */ |
198 | protected function getAliasFilepath(): string |
199 | { |
200 | return sprintf( |
201 | '%scomposer/autoload_aliases.php', |
202 | $this->config->getVendorDirectory() |
203 | ); |
204 | } |
205 | |
206 | protected function getModifiedSymbols(DiscoveredSymbols $symbols): DiscoveredSymbols |
207 | { |
208 | $modifiedSymbols = new DiscoveredSymbols(); |
209 | foreach ($symbols->getAll() as $symbol) { |
210 | if ($symbol->getOriginalSymbol() !== $symbol->getReplacement()) { |
211 | $modifiedSymbols->add($symbol); |
212 | } |
213 | } |
214 | return $modifiedSymbols; |
215 | } |
216 | |
217 | protected function registerAutoloader(array $classmap): void |
218 | { |
219 | |
220 | // Need to autoload the classes for reflection to work (this is maybe just an issue during tests). |
221 | spl_autoload_register(function (string $class) use ($classmap) { |
222 | if (isset($classmap[$class])) { |
223 | $this->logger->debug("Autoloading $class from {$classmap[$class]}"); |
224 | try { |
225 | include_once $classmap[$class]; |
226 | } catch (\Throwable $e) { |
227 | if (false !== strpos($e->getMessage(), 'PHPUnit')) { |
228 | $this->logger->warning("Error autoloading $class from {$classmap[$class]}: " . $e->getMessage()); |
229 | } else { |
230 | $this->logger->error("Error autoloading $class from {$classmap[$class]}: " . $e->getMessage()); |
231 | } |
232 | } |
233 | } |
234 | }); |
235 | } |
236 | |
237 | protected function buildStringOfAliases(DiscoveredSymbols $symbols, string $outputFilename): string |
238 | { |
239 | // TODO: When target !== vendor, there should be a test here to ensure the target autoloader is included, with instructions to add it. |
240 | |
241 | $modifiedSymbols = $this->getModifiedSymbols($symbols); |
242 | |
243 | $functionSymbols = $modifiedSymbols->getDiscoveredFunctions(); |
244 | |
245 | $autoloadAliasesFunctionsString = count($functionSymbols)>0 |
246 | ? $this->getFunctionAliasesString($functionSymbols) |
247 | : null; |
248 | $aliasesArray = $this->getAliasesArray($symbols); |
249 | |
250 | $autoloadAliasesFileString = $this->getTemplate($aliasesArray, $autoloadAliasesFunctionsString); |
251 | |
252 | return $autoloadAliasesFileString; |
253 | } |
254 | |
255 | /** |
256 | * @param array<NamespaceSymbol|ClassSymbol> $modifiedSymbols |
257 | * @param array $sourceDirClassmap |
258 | * @param array $targetDirClasssmap |
259 | * @return array{} |
260 | * @throws \League\Flysystem\FilesystemException |
261 | */ |
262 | protected function getAliasesArray(DiscoveredSymbols $symbols): array |
263 | { |
264 | $result = []; |
265 | |
266 | foreach ($symbols->getAll() as $originalSymbolFqdn => $symbol) { |
267 | if ($symbol->getOriginalSymbol() === $symbol->getReplacement()) { |
268 | continue; |
269 | } |
270 | if (!($symbol instanceof AutoloadAliasInterface)) { |
271 | continue; |
272 | } |
273 | $result[$originalSymbolFqdn] = $symbol->getAutoloadAliasArray(); |
274 | } |
275 | |
276 | return $result; |
277 | } |
278 | |
279 | protected function getFunctionAliasesString(array $modifiedSymbols): string |
280 | { |
281 | $autoloadAliasesFileString = ''; |
282 | |
283 | foreach ($modifiedSymbols as $symbol) { |
284 | $aliasesPhpString = ''; |
285 | |
286 | $originalSymbol = $symbol->getOriginalSymbol(); |
287 | $replacementSymbol = $symbol->getReplacement(); |
288 | |
289 | // if (!$symbol->getSourceFile()->isDoDelete()) { |
290 | // $this->logger->debug("Skipping {$originalSymbol} because it is not marked for deletion."); |
291 | // continue; |
292 | // } |
293 | |
294 | if ($originalSymbol === $replacementSymbol) { |
295 | $this->logger->debug("Skipping {$originalSymbol} because it is not being changed."); |
296 | continue; |
297 | } |
298 | |
299 | switch (get_class($symbol)) { |
300 | case FunctionSymbol::class: |
301 | // TODO: Do we need to check for `void`? Or will it just be ignored? |
302 | // Is it possible to inherit PHPDoc from the original function? |
303 | $aliasesPhpString = <<<EOD |
304 | if(!function_exists('$originalSymbol')){ |
305 | function $originalSymbol(...\$args) { return $replacementSymbol(func_get_args()); } |
306 | } |
307 | EOD; |
308 | break; |
309 | case ConstantSymbol::class: |
310 | /** |
311 | * https://stackoverflow.com/questions/19740621/namespace-constants-and-use-as |
312 | */ |
313 | // Ideally this would somehow be loaded after everything else. |
314 | // Maybe some Patchwork style redefining of `define()` to add the alias? |
315 | // Does it matter since all references to use the constant should have been updated to the new name anyway. |
316 | // TODO: global `const`. |
317 | $aliasesPhpString = <<<EOD |
318 | if(!defined('$originalSymbol') && defined('$replacementSymbol')) { |
319 | define('$originalSymbol', $replacementSymbol); |
320 | } |
321 | EOD; |
322 | break; |
323 | default: |
324 | /** |
325 | * Should be addressed above. |
326 | * |
327 | * @see self::appendAliasString()) |
328 | */ |
329 | break; |
330 | } |
331 | |
332 | $autoloadAliasesFileString .= $aliasesPhpString; |
333 | } |
334 | |
335 | return $autoloadAliasesFileString; |
336 | } |
337 | } |