From 28ed6b4a16756ae90d214eceff3c13e9ae439416 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Wed, 25 Mar 2020 04:13:23 +0100 Subject: [PATCH 1/4] function to change the name path of the template Template.tpl.php --- src/Doctrine/EntityClassGenerator.php | 4 +- src/Generator.php | 108 ++++++++++++++++++++++++-- 2 files changed, 105 insertions(+), 7 deletions(-) diff --git a/src/Doctrine/EntityClassGenerator.php b/src/Doctrine/EntityClassGenerator.php index 190f57d80..fd3c9ad74 100644 --- a/src/Doctrine/EntityClassGenerator.php +++ b/src/Doctrine/EntityClassGenerator.php @@ -36,7 +36,7 @@ public function generateEntityClass(ClassNameDetails $entityClassDetails, bool $ $entityPath = $this->generator->generateClass( $entityClassDetails->getFullName(), - 'doctrine/Entity.tpl.php', + $this->generator->getTemplateNameEntity(), [ 'repository_full_class_name' => $repoClassDetails->getFullName(), 'api_resource' => $apiResource, @@ -46,7 +46,7 @@ public function generateEntityClass(ClassNameDetails $entityClassDetails, bool $ $entityAlias = strtolower($entityClassDetails->getShortName()[0]); $this->generator->generateClass( $repoClassDetails->getFullName(), - 'doctrine/Repository.tpl.php', + $this->generator->getTemplateNameRepository(), [ 'entity_full_class_name' => $entityClassDetails->getFullName(), 'entity_class_name' => $entityClassDetails->getShortName(), diff --git a/src/Generator.php b/src/Generator.php index 7bab4b188..e1e830669 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -18,6 +18,7 @@ /** * @author Javier Eguiluz * @author Ryan Weaver + * @editor jonathan Kablan */ class Generator { @@ -25,12 +26,24 @@ class Generator private $twigHelper; private $pendingOperations = []; private $namespacePrefix; + private $templateNameEntity; + private $templateNameRepository; + private $rootTemplateName; + /** + * Generator constructor. + * @param FileManager $fileManager + * @param string $namespacePrefix + */ public function __construct(FileManager $fileManager, string $namespacePrefix) { $this->fileManager = $fileManager; $this->twigHelper = new GeneratorTwigHelper($fileManager); $this->namespacePrefix = trim($namespacePrefix, '\\'); + + $this->rootTemplateName = __DIR__.'/Resources/skeleton/'; + $this->templateNameEntity = 'doctrine/Entity.tpl.php'; + $this->templateNameRepository = 'doctrine/Repository.tpl.php'; } /** @@ -64,6 +77,11 @@ public function generateClass(string $className, string $templateName, array $va /** * Generate a normal file from a template. + * + * @param string $targetPath + * @param string $templateName + * @param array $variables + * @throws \Exception */ public function generateFile(string $targetPath, string $templateName, array $variables) { @@ -120,9 +138,12 @@ public function getFileContentsForPendingOperation(string $targetPath): string * // Cool\Stuff\BalloonController * $gen->createClassNameDetails('Cool\\Stuff\\Balloon', 'Controller', 'Controller'); * - * @param string $name The short "name" that will be turned into the class name - * @param string $namespacePrefix Recommended namespace where this class should live, but *without* the "App\\" part - * @param string $suffix Optional suffix to guarantee is on the end of the class + * @param string $name The short "name" that will be turned into the class name + * @param string $namespacePrefix Recommended namespace where this class should live, but *without* the "App\\" part + * @param string $suffix Optional suffix to guarantee is on the end of the class + * @param string $validationErrorMessage + * + * @return ClassNameDetails */ public function createClassNameDetails(string $name, string $namespacePrefix, string $suffix = '', string $validationErrorMessage = ''): ClassNameDetails { @@ -145,22 +166,34 @@ public function createClassNameDetails(string $name, string $namespacePrefix, st return new ClassNameDetails($className, $fullNamespacePrefix, $suffix); } + /** + * @return string + */ public function getRootDirectory(): string { return $this->fileManager->getRootDirectory(); } + /** + * @param string $targetPath + * @param string $templateName + * @param array $variables + * @throws \Exception + */ private function addOperation(string $targetPath, string $templateName, array $variables) { if ($this->fileManager->fileExists($targetPath)) { - throw new RuntimeCommandException(sprintf('The file "%s" can\'t be generated because it already exists.', $this->fileManager->relativizePath($targetPath))); + throw new RuntimeCommandException(sprintf( + 'The file "%s" can\'t be generated because it already exists.', + $this->fileManager->relativizePath($targetPath) + )); } $variables['relative_path'] = $this->fileManager->relativizePath($targetPath); $templatePath = $templateName; if (!file_exists($templatePath)) { - $templatePath = __DIR__.'/Resources/skeleton/'.$templateName; + $templatePath = $this->rootTemplateName.$templateName; if (!file_exists($templatePath)) { throw new \Exception(sprintf('Cannot find template "%s"', $templateName)); @@ -173,6 +206,9 @@ private function addOperation(string $targetPath, string $templateName, array $v ]; } + /** + * @return bool + */ public function hasPendingOperations(): bool { return !empty($this->pendingOperations); @@ -199,11 +235,21 @@ public function writeChanges() $this->pendingOperations = []; } + /** + * @return string + */ public function getRootNamespace(): string { return $this->namespacePrefix; } + /** + * @param string $controllerClassName + * @param string $controllerTemplatePath + * @param array $parameters + * @return string + * @throws \Exception + */ public function generateController(string $controllerClassName, string $controllerTemplatePath, array $parameters = []): string { return $this->generateClass( @@ -218,6 +264,10 @@ public function generateController(string $controllerClassName, string $controll /** * Generate a template file. + * + * @param string $targetPath + * @param string $templateName + * @param array $variables */ public function generateTemplate(string $targetPath, string $templateName, array $variables) { @@ -227,4 +277,52 @@ public function generateTemplate(string $targetPath, string $templateName, array $variables ); } + + /** + * @return string + */ + public function getTemplateNameEntity(): string + { + return $this->templateNameEntity; + } + + /** + * @param string $templateNameEntity + */ + public function setTemplateNameEntity(string $templateNameEntity) + { + $this->templateNameEntity = $templateNameEntity; + } + + /** + * @return string + */ + public function getTemplateNameRepository(): string + { + return $this->templateNameRepository; + } + + /** + * @param string $templateNameRepository + */ + public function setTemplateNameRepository(string $templateNameRepository) + { + $this->templateNameRepository = $templateNameRepository; + } + + /** + * @return string + */ + public function getRootTemplateName(): string + { + return $this->rootTemplateName; + } + + /** + * @param string $rootTemplateName + */ + public function setRootTemplateName(string $rootTemplateName) + { + $this->rootTemplateName = $rootTemplateName; + } } From 824e4ac3bfa8dbfbcda77720c53450d16f9ca86e Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sat, 28 Mar 2020 18:56:03 +0100 Subject: [PATCH 2/4] Update property in rootTemplateDirectory and removal (templatesNameEntity - templatesNameRepository) --- src/Doctrine/EntityClassGenerator.php | 254 +++++++++++++++++++++++--- 1 file changed, 225 insertions(+), 29 deletions(-) diff --git a/src/Doctrine/EntityClassGenerator.php b/src/Doctrine/EntityClassGenerator.php index fd3c9ad74..9d041736c 100644 --- a/src/Doctrine/EntityClassGenerator.php +++ b/src/Doctrine/EntityClassGenerator.php @@ -9,52 +9,248 @@ * file that was distributed with this source code. */ -namespace Symfony\Bundle\MakerBundle\Doctrine; +namespace Symfony\Bundle\MakerBundle; -use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; /** - * @internal + * @author Javier Eguiluz + * @author Ryan Weaver + * @editor Jonathan Kablan */ -final class EntityClassGenerator +class Generator { - private $generator; + private $fileManager; + private $twigHelper; + private $pendingOperations = []; + private $namespacePrefix; + private $rootTemplateDirectory; - public function __construct(Generator $generator) + public function __construct(FileManager $fileManager, string $namespacePrefix) { - $this->generator = $generator; + $this->fileManager = $fileManager; + $this->twigHelper = new GeneratorTwigHelper($fileManager); + $this->namespacePrefix = trim($namespacePrefix, '\\'); + $this->rootTemplateDirectory = __DIR__.'/Resources/skeleton/'; } - public function generateEntityClass(ClassNameDetails $entityClassDetails, bool $apiResource, bool $withPasswordUpgrade = false): string + /** + * Generate a new file for a class from a template. + * + * @param string $className The fully-qualified class name + * @param string $templateName Template name in Resources/skeleton to use + * @param array $variables Array of variables to pass to the template + * + * @return string The path where the file will be created + * + * @throws \Exception + */ + public function generateClass(string $className, string $templateName, array $variables = []): string { - $repoClassDetails = $this->generator->createClassNameDetails( - $entityClassDetails->getRelativeName(), - 'Repository\\', - 'Repository' - ); + $targetPath = $this->fileManager->getRelativePathForFutureClass($className); + + if (null === $targetPath) { + throw new \LogicException(sprintf('Could not determine where to locate the new class "%s", maybe try with a full namespace like "\\My\\Full\\Namespace\\%s"', $className, Str::getShortClassName($className))); + } + + $variables = array_merge($variables, [ + 'class_name' => Str::getShortClassName($className), + 'namespace' => Str::getNamespace($className), + ]); + + $this->addOperation($targetPath, $templateName, $variables); + + return $targetPath; + } + + /** + * Generate a normal file from a template. + * + * @param string $targetPath + * @param string $templateName + * @param array $variables + * @throws \Exception + */ + public function generateFile(string $targetPath, string $templateName, array $variables) + { + $variables = array_merge($variables, [ + 'helper' => $this->twigHelper, + ]); + + $this->addOperation($targetPath, $templateName, $variables); + } + + public function dumpFile(string $targetPath, string $contents) + { + $this->pendingOperations[$targetPath] = [ + 'contents' => $contents, + ]; + } + + public function getFileContentsForPendingOperation(string $targetPath): string + { + if (!isset($this->pendingOperations[$targetPath])) { + throw new RuntimeCommandException(sprintf('File "%s" is not in the Generator\'s pending operations', $targetPath)); + } + + $templatePath = $this->pendingOperations[$targetPath]['template']; + $parameters = $this->pendingOperations[$targetPath]['variables']; + + $templateParameters = array_merge($parameters, [ + 'relative_path' => $this->fileManager->relativizePath($targetPath), + ]); + + return $this->fileManager->parseTemplate($templatePath, $templateParameters); + } + + /** + * Creates a helper object to get data about a class name. + * + * Examples: + * + * // App\Entity\FeaturedProduct + * $gen->createClassNameDetails('FeaturedProduct', 'Entity'); + * $gen->createClassNameDetails('featured product', 'Entity'); + * + * // App\Controller\FooController + * $gen->createClassNameDetails('foo', 'Controller', 'Controller'); + * + * // App\Controller\Admin\FooController + * $gen->createClassNameDetails('Foo\\Admin', 'Controller', 'Controller'); + * + * // App\Controller\Security\Voter\CoolController + * $gen->createClassNameDetails('Cool', 'Security\Voter', 'Voter'); + * + * // Full class names can also be passed. Imagine the user has an autoload + * // rule where Cool\Stuff lives in a "lib/" directory + * // Cool\Stuff\BalloonController + * $gen->createClassNameDetails('Cool\\Stuff\\Balloon', 'Controller', 'Controller'); + * + * @param string $name The short "name" that will be turned into the class name + * @param string $namespacePrefix Recommended namespace where this class should live, but *without* the "App\\" part + * @param string $suffix Optional suffix to guarantee is on the end of the class + * @param string $validationErrorMessage + * + * @return ClassNameDetails + */ + public function createClassNameDetails(string $name, string $namespacePrefix, string $suffix = '', string $validationErrorMessage = ''): ClassNameDetails + { + $fullNamespacePrefix = $this->namespacePrefix.'\\'.$namespacePrefix; + if ('\\' === $name[0]) { + // class is already "absolute" - leave it alone (but strip opening \) + $className = substr($name, 1); + } else { + $className = rtrim($fullNamespacePrefix, '\\').'\\'.Str::asClassName($name, $suffix); + } + + Validator::validateClassName($className, $validationErrorMessage); + + // if this is a custom class, we may be completely different than the namespace prefix + // the best way can do, is find the PSR4 prefix and use that + if (0 !== strpos($className, $fullNamespacePrefix)) { + $fullNamespacePrefix = $this->fileManager->getNamespacePrefixForClass($className); + } + + return new ClassNameDetails($className, $fullNamespacePrefix, $suffix); + } + + public function getRootDirectory(): string + { + return $this->fileManager->getRootDirectory(); + } + + private function addOperation(string $targetPath, string $templateName, array $variables) + { + if ($this->fileManager->fileExists($targetPath)) { + throw new RuntimeCommandException(sprintf( + 'The file "%s" can\'t be generated because it already exists.', + $this->fileManager->relativizePath($targetPath) + )); + } + + $variables['relative_path'] = $this->fileManager->relativizePath($targetPath); + + $templatePath = $templateName; + if (!file_exists($templatePath)) { + $templatePath = $this->rootTemplateDirectory.$templateName; + + if (!file_exists($templatePath)) { + throw new \Exception(sprintf('Cannot find template "%s"', $templateName)); + } + } + + $this->pendingOperations[$targetPath] = [ + 'template' => $templatePath, + 'variables' => $variables, + ]; + } + + public function hasPendingOperations(): bool + { + return !empty($this->pendingOperations); + } + + /** + * Actually writes and file changes that are pending. + */ + public function writeChanges() + { + foreach ($this->pendingOperations as $targetPath => $templateData) { + if (isset($templateData['contents'])) { + $this->fileManager->dumpFile($targetPath, $templateData['contents']); + + continue; + } - $entityPath = $this->generator->generateClass( - $entityClassDetails->getFullName(), - $this->generator->getTemplateNameEntity(), + $this->fileManager->dumpFile( + $targetPath, + $this->getFileContentsForPendingOperation($targetPath, $templateData) + ); + } + + $this->pendingOperations = []; + } + + public function getRootNamespace(): string + { + return $this->namespacePrefix; + } + + public function generateController(string $controllerClassName, string $controllerTemplatePath, array $parameters = []): string + { + return $this->generateClass( + $controllerClassName, + $controllerTemplatePath, + $parameters + [ - 'repository_full_class_name' => $repoClassDetails->getFullName(), - 'api_resource' => $apiResource, + 'parent_class_name' => method_exists(AbstractController::class, 'getParameter') ? 'AbstractController' : 'Controller', ] ); + } - $entityAlias = strtolower($entityClassDetails->getShortName()[0]); - $this->generator->generateClass( - $repoClassDetails->getFullName(), - $this->generator->getTemplateNameRepository(), - [ - 'entity_full_class_name' => $entityClassDetails->getFullName(), - 'entity_class_name' => $entityClassDetails->getShortName(), - 'entity_alias' => $entityAlias, - 'with_password_upgrade' => $withPasswordUpgrade, - ] + /** + * Generate a template file. + * + * @param string $targetPath + * @param string $templateName + * @param array $variables + */ + public function generateTemplate(string $targetPath, string $templateName, array $variables) + { + $this->generateFile( + $this->fileManager->getPathForTemplate($targetPath), + $templateName, + $variables ); + } - return $entityPath; + /** + * @param string $rootTemplateDirectory + */ + public function setRootTemplateDirectory(string $rootTemplateDirectory) + { + $this->rootTemplateDirectory = $rootTemplateDirectory; } } From 945014713f65032081f7b087ca3f3d32d15b6e03 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 9 Apr 2020 03:26:05 +0200 Subject: [PATCH 3/4] new composant MakerRender Interface --- src/MakerRenderer.php | 21 +++++++++++++++++++++ src/MakerTemplateRendererInterface.php | 22 ++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/MakerRenderer.php create mode 100644 src/MakerTemplateRendererInterface.php diff --git a/src/MakerRenderer.php b/src/MakerRenderer.php new file mode 100644 index 000000000..90198e727 --- /dev/null +++ b/src/MakerRenderer.php @@ -0,0 +1,21 @@ +supports($templateName); + } +} \ No newline at end of file diff --git a/src/MakerTemplateRendererInterface.php b/src/MakerTemplateRendererInterface.php new file mode 100644 index 000000000..9bf252311 --- /dev/null +++ b/src/MakerTemplateRendererInterface.php @@ -0,0 +1,22 @@ + + */ +interface MakerTemplateRendererInterface +{ + /** + * @param string $templateName + * @return string + */ + public function supports(string $templateName): string; + + /** + * @param string $templateName + * @return string + */ + public function render(string $templateName = null): string; +} \ No newline at end of file From 8a371ef2c24cc7edf8368b0ce21f828482dc9b30 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 10 Apr 2020 00:43:39 +0200 Subject: [PATCH 4/4] Create Class MakerDefaultTemplateRenderer --- src/MakerDefaultTemplateRenderer.php | 46 ++++++++++++++++++++++++++ src/MakerRenderer.php | 31 ++++++++++++----- src/MakerTemplateRendererInterface.php | 7 ++-- 3 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 src/MakerDefaultTemplateRenderer.php diff --git a/src/MakerDefaultTemplateRenderer.php b/src/MakerDefaultTemplateRenderer.php new file mode 100644 index 000000000..e28819da4 --- /dev/null +++ b/src/MakerDefaultTemplateRenderer.php @@ -0,0 +1,46 @@ +fileManager = $fileManager; + } + + /** + * @param string $templateName + * @return bool + */ + public function supports(string $templateName): bool + { + $templatePath = __DIR__.'/Resources/skeleton/'.$templateName; + + return file_exists($templatePath); + } + + /** + * @param string $templateName + * @param array $variables + * @return string + */ + public function render(string $templateName, array $variables): string + { + $templatePath = __DIR__.'/Resources/skeleton/'.$templateName; + + // this is basically the current logic for rendering templates + $contents = $this->fileManager->parseTemplate($templatePath, $variables); + $this->fileManager->dumpFile($templatePath, $contents); + + return $templatePath; + } +} \ No newline at end of file diff --git a/src/MakerRenderer.php b/src/MakerRenderer.php index 90198e727..d26058b57 100644 --- a/src/MakerRenderer.php +++ b/src/MakerRenderer.php @@ -1,21 +1,36 @@ templateRenderers = $templateRenderers; } - public function render(string $templateName = null): string + /** + * @param string $templateName + * @param array $variables + * @return string + * @throws \Exception + */ + public function renderTemplate(string $templateName, array $variables) { - if (null === $templateName) { - return __DIR__.'/Resources/skeleton/'; + foreach ($this->templateRenderers as $templateRenderer) { + if ($templateRenderer->supports($templateName)) { + return $templateRenderer->render($templateName, $variables); + } } - return $this->supports($templateName); + throw new \Exception(sprintf('No template renderers for template "%s"', $templateName)); } } \ No newline at end of file diff --git a/src/MakerTemplateRendererInterface.php b/src/MakerTemplateRendererInterface.php index 9bf252311..e2c58385a 100644 --- a/src/MakerTemplateRendererInterface.php +++ b/src/MakerTemplateRendererInterface.php @@ -10,13 +10,14 @@ interface MakerTemplateRendererInterface { /** * @param string $templateName - * @return string + * @return bool */ - public function supports(string $templateName): string; + public function supports(string $templateName): bool; /** * @param string $templateName + * @param array $variables * @return string */ - public function render(string $templateName = null): string; + public function render(string $templateName, array $variables): string; } \ No newline at end of file