From 002f7ccdc655241566d86c9cf89413e75d7e21b5 Mon Sep 17 00:00:00 2001 From: Silas Joisten Date: Thu, 15 May 2025 11:19:44 +0200 Subject: [PATCH 1/2] feat(refactor): Use template classes --- generator/src/Generator/FileCreator.php | 111 ++++++------------ generator/src/Templating/Engine.php | 61 ++++++++++ .../Exception/InvalidPlaceholderException.php | 9 ++ .../Exception/TemplateNotFoundException.php | 9 ++ .../Exception/TemplatingException.php | 7 ++ .../Exception/UnreadableTemplateException.php | 9 ++ .../Templating/templates/Exception.php.tpl | 11 ++ .../Templating/templates/FunctionList.php.tpl | 5 + .../src/Templating/templates/Module.php.tpl | 7 ++ .../Templating/templates/RectorConfig.php.tpl | 16 +++ 10 files changed, 167 insertions(+), 78 deletions(-) create mode 100644 generator/src/Templating/Engine.php create mode 100644 generator/src/Templating/Exception/InvalidPlaceholderException.php create mode 100644 generator/src/Templating/Exception/TemplateNotFoundException.php create mode 100644 generator/src/Templating/Exception/TemplatingException.php create mode 100644 generator/src/Templating/Exception/UnreadableTemplateException.php create mode 100644 generator/src/Templating/templates/Exception.php.tpl create mode 100644 generator/src/Templating/templates/FunctionList.php.tpl create mode 100644 generator/src/Templating/templates/Module.php.tpl create mode 100644 generator/src/Templating/templates/RectorConfig.php.tpl diff --git a/generator/src/Generator/FileCreator.php b/generator/src/Generator/FileCreator.php index 47935d0a..89694e0c 100644 --- a/generator/src/Generator/FileCreator.php +++ b/generator/src/Generator/FileCreator.php @@ -4,6 +4,7 @@ namespace Safe\Generator; +use Safe\Templating\Engine; use Safe\XmlDocParser\ErrorType; use Safe\XmlDocParser\Scanner; use Safe\XmlDocParser\Method; @@ -13,6 +14,12 @@ class FileCreator { + private Engine $engine; + + public function __construct(?Engine $engine = null) + { + $this->engine = $engine ?? new Engine(); + } /** * This function generate an improved php lib function in a php file @@ -34,26 +41,14 @@ public function generatePhpFile( foreach ($phpFunctionsByModule as $module => $phpFunctions) { $lcModule = \lcfirst($module); - if (!is_dir($path)) { + if (!\is_dir($path)) { \mkdir($path); } - $stream = \fopen($path.$lcModule.'.php', 'w'); - if ($stream === false) { - throw new \RuntimeException('Unable to write to '.$path); - } - - // Write file header - \fwrite($stream, "dumpTemplate($path . $lcModule . '.php', 'Module.php.tpl', [ + '{{exceptionName}}' => self::toExceptionName($module), + '{{functions}}' => \implode(PHP_EOL, $phpFunctions), + ]); } } @@ -107,18 +102,9 @@ private function getFunctionsNameList(array $functions): array */ public function generateFunctionsList(array $functions, string $path): void { - $functionNames = $this->getFunctionsNameList($functions); - $stream = fopen($path, 'w'); - if ($stream === false) { - throw new \RuntimeException('Unable to write to '.$path); - } - fwrite($stream, "dumpTemplate($path, 'FunctionList.php.tpl', [ + '{{functionNames}}' => \implode(PHP_EOL, \array_map(static fn(string $name): string => \sprintf('\'%s\',', $name), $this->getFunctionsNameList($functions))), + ]); } /** @@ -128,61 +114,18 @@ public function generateFunctionsList(array $functions, string $path): void */ public function generateRectorFile(array $functions, string $path): void { - $functionNames = $this->getFunctionsNameList($functions); - - $stream = fopen($path, 'w'); - - if ($stream === false) { - throw new \RuntimeException('Unable to write to '.$path); - } - - $header = <<<'TXT' -ruleWithConfiguration( - RenameFunctionRector::class, - [ -TXT; - - fwrite($stream, $header); - - foreach ($functionNames as $functionName) { - fwrite($stream, " '$functionName' => 'Safe\\$functionName',\n"); - } - - fwrite($stream, " ]\n );\n};\n"); - fclose($stream); + $this->dumpTemplate($path, 'RectorConfig.php.tpl', [ + '{{functionNames}}' => \implode(PHP_EOL, \array_map(static fn(string $name): string => \sprintf('\'%1$s\' => \'Safe\\%1$s\',', $name), $this->getFunctionsNameList($functions))), + ]); } public function createExceptionFile(string $moduleName): void { $exceptionName = self::toExceptionName($moduleName); - if (!file_exists(FileCreator::getSafeRootDir() . '/lib/Exceptions/'.$exceptionName.'.php')) { - \file_put_contents( - FileCreator::getSafeRootDir() . '/generated/Exceptions/'.$exceptionName.'.php', - <<dumpTemplate(FileCreator::getSafeRootDir() . '/generated/Exceptions/'.$exceptionName.'.php', 'Exception.php.tpl', [ + '{{exceptionName}}' => $exceptionName, + ]); } public static function getSafeRootDir(): string @@ -197,4 +140,16 @@ public static function toExceptionName(string $moduleName): string { return str_replace('-', '', \ucfirst($moduleName)).'Exception'; } + + /** + * @param array $context + */ + private function dumpTemplate(string $target, string $template, array $context = []): void + { + $result = file_put_contents($target, $this->engine->generate($template, $context)); + + if (false === $result) { + throw new \RuntimeException(\sprintf('Could not write to "%s".', $target)); + } + } } diff --git a/generator/src/Templating/Engine.php b/generator/src/Templating/Engine.php new file mode 100644 index 00000000..eccb8ace --- /dev/null +++ b/generator/src/Templating/Engine.php @@ -0,0 +1,61 @@ +files()->name('*.php.tpl')->in(self::basePath()); + $this->templates = array_map(fn(\SplFileInfo $file) => str_replace(self::basePath() . '/', '', $file->getRealPath()), \iterator_to_array($finder->getIterator())); + } + + /** + * @param array $context + * + * @throws TemplatingException + */ + public function generate(string $template, array $context = []): string + { + if (!$this->hasTemplate($template)) { + throw new TemplateNotFoundException(\sprintf('Template "%s" not found.', $template)); + } + + if (false === $content = file_get_contents(self::basePath() . '/' . $template)) { + throw new UnreadableTemplateException(\sprintf('Could not read template "%s".', $template)); + } + + foreach ($context as $placeholder => $replacement) { + if (!\is_string($replacement)) { + throw new InvalidPlaceholderException(\sprintf('Placeholder "%s" must be a string.', $placeholder)); + } + + $content = str_replace($placeholder, $replacement, $content); + } + + return $content; + } + + private function hasTemplate(string $name): bool + { + return 0 < \count(\array_filter($this->templates, static fn(string $template) => $template === $name)); + } + + private static function basePath(): string + { + return __DIR__ . '/templates'; + } +} diff --git a/generator/src/Templating/Exception/InvalidPlaceholderException.php b/generator/src/Templating/Exception/InvalidPlaceholderException.php new file mode 100644 index 00000000..f5c7d089 --- /dev/null +++ b/generator/src/Templating/Exception/InvalidPlaceholderException.php @@ -0,0 +1,9 @@ +ruleWithConfiguration( + RenameFunctionRector::class, + [ + {{functionNames}} + ], + ); +}; \ No newline at end of file From 85c4ba4f416b6a4642dbfafe20b3aa9b3649e58e Mon Sep 17 00:00:00 2001 From: Silas Joisten Date: Thu, 15 May 2025 14:02:30 +0200 Subject: [PATCH 2/2] Fix --- generator/src/Commands/GenerateCommand.php | 7 ++-- .../src/Generator/ComposerJsonEditor.php | 6 ++- generator/src/Generator/FileCreator.php | 37 +++++++--------- .../PhpStanFunctionMapReader.php | 5 ++- generator/src/Templating/Engine.php | 2 +- generator/src/Templating/Filesystem.php | 42 +++++++++++++++++++ generator/src/XmlDocParser/DocPage.php | 3 +- generator/src/XmlDocParser/Scanner.php | 18 ++++---- .../src/XmlDocParser/ScannerResponse.php | 5 +++ .../templates/Exception.php.tpl | 4 ++ .../templates/FunctionList.php.tpl | 0 .../Templating => }/templates/Module.php.tpl | 0 .../templates/RectorConfig.php.tpl | 0 13 files changed, 90 insertions(+), 39 deletions(-) create mode 100644 generator/src/Templating/Filesystem.php rename generator/{src/Templating => }/templates/Exception.php.tpl (91%) rename generator/{src/Templating => }/templates/FunctionList.php.tpl (100%) rename generator/{src/Templating => }/templates/Module.php.tpl (100%) rename generator/{src/Templating => }/templates/RectorConfig.php.tpl (100%) diff --git a/generator/src/Commands/GenerateCommand.php b/generator/src/Commands/GenerateCommand.php index 0e7efba7..0ce02359 100644 --- a/generator/src/Commands/GenerateCommand.php +++ b/generator/src/Commands/GenerateCommand.php @@ -83,7 +83,7 @@ protected function execute( $modules[$function->getModuleName()] = true; } - $genDir = FileCreator::getSafeRootDir() . "/generated/$version"; + $genDir = Filesystem::outputDir() . "/$version"; $fileCreator = new FileCreator(); $fileCreator->generatePhpFile($res->methods, "$genDir/"); $fileCreator->generateFunctionsList($res->methods, "$genDir/functionsList.php"); @@ -91,10 +91,11 @@ protected function execute( } foreach (\array_keys($modules) as $moduleName) { - $fileCreator->generateVersionSplitters($moduleName, FileCreator::getSafeRootDir() . "/generated/", \array_keys($versions)); + $fileCreator->generateVersionSplitters($moduleName, Filesystem::outputDir() . "/", \array_keys($versions)); $fileCreator->createExceptionFile((string) $moduleName); } - $fileCreator->generateVersionSplitters("functionsList", FileCreator::getSafeRootDir() . "/generated/", \array_keys($versions), true); + + $fileCreator->generateVersionSplitters("functionsList", Filesystem::outputDir() . "/", \array_keys($versions), true); $this->runCsFix($output); diff --git a/generator/src/Generator/ComposerJsonEditor.php b/generator/src/Generator/ComposerJsonEditor.php index 94ee9a04..7721bd07 100644 --- a/generator/src/Generator/ComposerJsonEditor.php +++ b/generator/src/Generator/ComposerJsonEditor.php @@ -4,6 +4,8 @@ namespace Safe\Generator; +use Safe\Templating\Filesystem; + /** * This class will edit the main composer.json file to add the list of files generated from modules. */ @@ -15,7 +17,7 @@ class ComposerJsonEditor public static function editComposerFileForGeneration(array $modules): void { - $composerContent = file_get_contents(FileCreator::getSafeRootDir() . '/composer.json'); + $composerContent = file_get_contents(Filesystem::projectRootDir() . '/composer.json'); if ($composerContent === false) { throw new \RuntimeException('Error while loading composer.json file for edition.'); } @@ -24,7 +26,7 @@ public static function editComposerFileForGeneration(array $modules): void $composerJson['autoload']['files'] = self::editFilesListForGeneration($composerJson['autoload']['files'], $modules); $newContent = \json_encode($composerJson, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES) . "\n"; - \file_put_contents(FileCreator::getSafeRootDir() . '/composer.json', $newContent); + \file_put_contents(Filesystem::projectRootDir() . '/composer.json', $newContent); } /** diff --git a/generator/src/Generator/FileCreator.php b/generator/src/Generator/FileCreator.php index 89694e0c..c5c8da27 100644 --- a/generator/src/Generator/FileCreator.php +++ b/generator/src/Generator/FileCreator.php @@ -5,6 +5,7 @@ namespace Safe\Generator; use Safe\Templating\Engine; +use Safe\Templating\Filesystem; use Safe\XmlDocParser\ErrorType; use Safe\XmlDocParser\Scanner; use Safe\XmlDocParser\Method; @@ -45,10 +46,10 @@ public function generatePhpFile( \mkdir($path); } - $this->dumpTemplate($path . $lcModule . '.php', 'Module.php.tpl', [ + Filesystem::dumpFile($path . $lcModule . '.php', $this->engine->generate('Module.php.tpl', [ '{{exceptionName}}' => self::toExceptionName($module), '{{functions}}' => \implode(PHP_EOL, $phpFunctions), - ]); + ])); } } @@ -102,9 +103,9 @@ private function getFunctionsNameList(array $functions): array */ public function generateFunctionsList(array $functions, string $path): void { - $this->dumpTemplate($path, 'FunctionList.php.tpl', [ + Filesystem::dumpFile($path, $this->engine->generate('FunctionList.php.tpl', [ '{{functionNames}}' => \implode(PHP_EOL, \array_map(static fn(string $name): string => \sprintf('\'%s\',', $name), $this->getFunctionsNameList($functions))), - ]); + ])); } /** @@ -114,23 +115,29 @@ public function generateFunctionsList(array $functions, string $path): void */ public function generateRectorFile(array $functions, string $path): void { - $this->dumpTemplate($path, 'RectorConfig.php.tpl', [ + Filesystem::dumpFile($path, $this->engine->generate('RectorConfig.php.tpl', [ '{{functionNames}}' => \implode(PHP_EOL, \array_map(static fn(string $name): string => \sprintf('\'%1$s\' => \'Safe\\%1$s\',', $name), $this->getFunctionsNameList($functions))), - ]); + ])); } public function createExceptionFile(string $moduleName): void { $exceptionName = self::toExceptionName($moduleName); - $this->dumpTemplate(FileCreator::getSafeRootDir() . '/generated/Exceptions/'.$exceptionName.'.php', 'Exception.php.tpl', [ + Filesystem::dumpFile(Filesystem::outputDir() . '/Exceptions/'.$exceptionName.'.php', $this->engine->generate('Exception.php.tpl', [ '{{exceptionName}}' => $exceptionName, - ]); + ])); } public static function getSafeRootDir(): string { - return __DIR__ . '/../../..'; + $path = realpath(__DIR__ . '/../../..'); + + if (false === $path) { + throw new \RuntimeException('Unable to locate root directory'); + } + + return $path; } /** @@ -140,16 +147,4 @@ public static function toExceptionName(string $moduleName): string { return str_replace('-', '', \ucfirst($moduleName)).'Exception'; } - - /** - * @param array $context - */ - private function dumpTemplate(string $target, string $template, array $context = []): void - { - $result = file_put_contents($target, $this->engine->generate($template, $context)); - - if (false === $result) { - throw new \RuntimeException(\sprintf('Could not write to "%s".', $target)); - } - } } diff --git a/generator/src/PhpStanFunctions/PhpStanFunctionMapReader.php b/generator/src/PhpStanFunctions/PhpStanFunctionMapReader.php index f4471b07..9882614f 100644 --- a/generator/src/PhpStanFunctions/PhpStanFunctionMapReader.php +++ b/generator/src/PhpStanFunctions/PhpStanFunctionMapReader.php @@ -5,6 +5,7 @@ namespace Safe\PhpStanFunctions; use Safe\Generator\FileCreator; +use Safe\Templating\Filesystem; class PhpStanFunctionMapReader { @@ -20,8 +21,8 @@ class PhpStanFunctionMapReader public function __construct() { - $this->functionMap = require 'phar://' . FileCreator::getSafeRootDir() . '/generator/vendor/phpstan/phpstan/phpstan.phar/resources/functionMap.php'; - $this->customFunctionMap = require FileCreator::getSafeRootDir() . '/generator/config/CustomPhpStanFunctionMap.php'; + $this->functionMap = require 'phar://' . Filesystem::generatorDir() . '/vendor/phpstan/phpstan/phpstan.phar/resources/functionMap.php'; + $this->customFunctionMap = require Filesystem::generatorDir() . '/config/CustomPhpStanFunctionMap.php'; } public function getFunction(string $functionName): ?PhpStanFunction diff --git a/generator/src/Templating/Engine.php b/generator/src/Templating/Engine.php index eccb8ace..f990f755 100644 --- a/generator/src/Templating/Engine.php +++ b/generator/src/Templating/Engine.php @@ -56,6 +56,6 @@ private function hasTemplate(string $name): bool private static function basePath(): string { - return __DIR__ . '/templates'; + return Filesystem::generatorDir().'/templates'; } } diff --git a/generator/src/Templating/Filesystem.php b/generator/src/Templating/Filesystem.php new file mode 100644 index 00000000..3d69a178 --- /dev/null +++ b/generator/src/Templating/Filesystem.php @@ -0,0 +1,42 @@ +extractSection('returnvalues', $file); - $detectErrorType = require FileCreator::getSafeRootDir() . '/generator/config/detectErrorType.php'; + $detectErrorType = require Filesystem::generatorDir() . '/config/detectErrorType.php'; return $detectErrorType($returnDocs); } diff --git a/generator/src/XmlDocParser/Scanner.php b/generator/src/XmlDocParser/Scanner.php index 3ac8438f..61a4aaa7 100644 --- a/generator/src/XmlDocParser/Scanner.php +++ b/generator/src/XmlDocParser/Scanner.php @@ -4,6 +4,8 @@ namespace Safe\XmlDocParser; +use Safe\Templating\Filesystem; +use Symfony\Component\Console\Style\SymfonyStyle; use function array_merge; use function iterator_to_array; use Safe\PhpStanFunctions\PhpStanFunctionMapReader; @@ -58,7 +60,7 @@ public function getMethodsPaths(): array private function getIgnoredFunctions(): array { if ($this->ignoredFunctions === null) { - $ignoredFunctions = require FileCreator::getSafeRootDir() . '/generator/config/ignoredFunctions.php'; + $ignoredFunctions = require Filesystem::generatorDir() . '/config/ignoredFunctions.php'; $specialCaseFunctions = $this->getSpecialCases(); $this->ignoredFunctions = array_merge($ignoredFunctions, $specialCaseFunctions); @@ -73,7 +75,7 @@ private function getIgnoredFunctions(): array private function getIgnoredModules(): array { if ($this->ignoredModules === null) { - $this->ignoredModules = require FileCreator::getSafeRootDir() . '/generator/config/ignoredModules.php'; + $this->ignoredModules = require Filesystem::generatorDir() . '/config/ignoredModules.php'; assert(!is_null($this->ignoredModules)); } return $this->ignoredModules; @@ -87,7 +89,7 @@ private function getIgnoredModules(): array */ public static function getSpecialCases(): array { - $data = file_get_contents(FileCreator::getSafeRootDir() . '/lib/special_cases.php'); + $data = file_get_contents(Filesystem::projectRootDir() . '/lib/special_cases.php'); if ($data === false) { throw new \RuntimeException('Unable to read special cases'); } @@ -104,14 +106,14 @@ public static function getSpecialCases(): array */ public static function getHiddenFunctions(): array { - return require FileCreator::getSafeRootDir() . '/generator/config/hiddenFunctions.php'; + return require Filesystem::generatorDir() . '/config/hiddenFunctions.php'; } /** * @param SplFileInfo[] $paths * @param string[] $pastFunctionNames */ - public function getMethods(array $paths, array $pastFunctionNames, OutputInterface $output): ScannerResponse + public function getMethods(array $paths, array $pastFunctionNames, SymfonyStyle $io): ScannerResponse { /** @var Method[] $functions */ $functions = []; @@ -124,9 +126,7 @@ public function getMethods(array $paths, array $pastFunctionNames, OutputInterfa $ignoredModules = $this->getIgnoredModules(); $ignoredModules = \array_combine($ignoredModules, $ignoredModules); - ProgressBar::setFormatDefinition('custom', ' %current%/%max% [%bar%] %message%'); - $progressBar = new ProgressBar($output, count($paths)); - $progressBar->setFormat("custom"); + $progressBar = $io->createProgressBar(\count($paths)); foreach ($paths as $path) { $module = \basename(\dirname($path->getPath())); $progressBar->setMessage($path->getFilename()); @@ -167,8 +167,8 @@ public function getMethods(array $paths, array $pastFunctionNames, OutputInterfa } } } + $progressBar->finish(); - $output->writeln(""); return new ScannerResponse($functions, $overloadedFunctions); } diff --git a/generator/src/XmlDocParser/ScannerResponse.php b/generator/src/XmlDocParser/ScannerResponse.php index e82af9c4..3c8ce42c 100644 --- a/generator/src/XmlDocParser/ScannerResponse.php +++ b/generator/src/XmlDocParser/ScannerResponse.php @@ -15,4 +15,9 @@ public function __construct( public readonly array $overloadedFunctions ) { } + + public function hasOverloadedFunctions(): bool + { + return \count($this->overloadedFunctions) > 0; + } } diff --git a/generator/src/Templating/templates/Exception.php.tpl b/generator/templates/Exception.php.tpl similarity index 91% rename from generator/src/Templating/templates/Exception.php.tpl rename to generator/templates/Exception.php.tpl index 8ce6582c..d664d65a 100644 --- a/generator/src/Templating/templates/Exception.php.tpl +++ b/generator/templates/Exception.php.tpl @@ -1,4 +1,7 @@