diff --git a/README.md b/README.md index 5c180e1..998199d 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ open_api_server: root_path: %kernel.project_dir%/src/Generated language_level: 7.4.0 # minimum PHP version the generated code should be compatible with generated_dir_permissions: 0755 # permissions for the generated directories + full_doc_blocks: false # whether to generate DocBlocks for typed variables and params specs: petstore: path: '../spec/petstore.yaml' # path to OpenApi specification diff --git a/src/CodeGenerator/ApiServerCodeGenerator.php b/src/CodeGenerator/ApiServerCodeGenerator.php index 867f032..570b04b 100644 --- a/src/CodeGenerator/ApiServerCodeGenerator.php +++ b/src/CodeGenerator/ApiServerCodeGenerator.php @@ -8,7 +8,6 @@ use OnMoon\OpenApiServerBundle\Event\CodeGenerator\ClassGraphReadyEvent; use OnMoon\OpenApiServerBundle\Event\CodeGenerator\FilesReadyEvent; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use function Safe\substr; class ApiServerCodeGenerator { @@ -37,24 +36,6 @@ public function generate() : void $this->interfaceGenerator->setAllInterfaces($graph); $this->attributeGenerator->setAllAttributes($graph); $this->nameGenerator->setAllNamesAndPaths($graph); - //ToDo: remove this loop - foreach ($graph->getSpecifications() as $specificationDefinition) { - foreach ($specificationDefinition->getOperations() as $operation) { - $request = $operation->getRequest(); - if ($request === null) { - continue; - } - - foreach ($request->getProperties() as $property) { - $object = $property->getObjectTypeDefinition(); - if ($object === null) { - continue; - } - - $this->nameGenerator->setTreePathsAndClassNames($object, $request->getNamespace(), substr($request->getClassName(), 0, -3) . $object->getClassName(), $request->getFilePath()); - } - } - } $this->eventDispatcher->dispatch(new ClassGraphReadyEvent($graph)); diff --git a/src/CodeGenerator/PhpParserGenerators/CodeGenerator.php b/src/CodeGenerator/PhpParserGenerators/CodeGenerator.php index 54af9ad..8db9337 100644 --- a/src/CodeGenerator/PhpParserGenerators/CodeGenerator.php +++ b/src/CodeGenerator/PhpParserGenerators/CodeGenerator.php @@ -31,13 +31,14 @@ abstract class CodeGenerator protected BuilderFactory $factory; protected ScalarTypesResolver $typeResolver; protected string $languageLevel; - protected bool $fullDocs = false; + protected bool $fullDocs; - public function __construct(BuilderFactory $factory, ScalarTypesResolver $typeResolver, string $languageLevel) + public function __construct(BuilderFactory $factory, ScalarTypesResolver $typeResolver, string $languageLevel, bool $fullDocs) { $this->factory = $factory; $this->typeResolver = $typeResolver; $this->languageLevel = $languageLevel; + $this->fullDocs = $fullDocs; } public function use(Namespace_ $builder, string $parentNameSpace, ClassDefinition $class) : void @@ -85,11 +86,10 @@ public function getDocComment(array $lines) : string $glued = ' ' . trim($lines[0]); } else { $asteriskLines = array_map( - //ToDo: add space after * anyway after tests - static fn(string $line) : string => ' *' . (trim($line)?' ':'') . trim($line), + static fn(string $line) : string => ' * ' . trim($line), $lines ); - $glued = PHP_EOL . implode(PHP_EOL, $asteriskLines) . PHP_EOL; + $glued = PHP_EOL . implode(PHP_EOL, $asteriskLines) . PHP_EOL; } return sprintf('/**%s */', $glued); diff --git a/src/CodeGenerator/PhpParserGenerators/DtoCodeGenerator.php b/src/CodeGenerator/PhpParserGenerators/DtoCodeGenerator.php index de20d42..da6815c 100644 --- a/src/CodeGenerator/PhpParserGenerators/DtoCodeGenerator.php +++ b/src/CodeGenerator/PhpParserGenerators/DtoCodeGenerator.php @@ -18,8 +18,6 @@ use PhpParser\Node\Stmt\Return_; use function count; use function Safe\sprintf; -use function str_replace; -use function strpos; class DtoCodeGenerator extends CodeGenerator { @@ -51,15 +49,14 @@ public function generate(DtoDefinition $definition) : GeneratedFileDefinition } $classBuilder->addStmts($this->generateProperties($definition)); - //ToDo: Move below after tests - if ($definition instanceof ResponseDtoDefinition) { - $classBuilder->addStmt($this->generateResponseCodeStaticMethod($definition)); - } - $classBuilder->addStmts($this->generateConstructor($definition)); $classBuilder->addStmts($this->generateGetters($definition)); $classBuilder->addStmts($this->generateSetters($definition)); + if ($definition instanceof ResponseDtoDefinition) { + $classBuilder->addStmt($this->generateResponseCodeStaticMethod($definition)); + } + $fileBuilder = $fileBuilder->addStmt($classBuilder); return new GeneratedFileDefinition( @@ -176,13 +173,14 @@ private function generateClassProperty(PropertyDefinition $definition) : Propert $docCommentLines[] = ''; } - //ToDo: remove this - if (strpos($definition->getClassPropertyName(), 'queryParameters') === false - && - strpos($definition->getClassPropertyName(), 'pathParameters') === false - && - strpos($definition->getClassPropertyName(), 'body') === false - ) { + $supportSymfonySerializer = true; + /* + * Symfony serializer does not support property class definitions. + * ToDo: Remove this hack and phpstan ignores after serializer is no longer used. + */ + + /** @phpstan-ignore-next-line */ + if ($this->fullDocs || $definition->isArray() || $supportSymfonySerializer) { $docCommentLines[] = sprintf( '@var %s $%s ', $this->getTypeDocBlock($definition), @@ -190,6 +188,7 @@ private function generateClassProperty(PropertyDefinition $definition) : Propert ); } + /** @phpstan-ignore-next-line */ if (count($docCommentLines)) { $property->setDocComment($this->getDocComment($docCommentLines)); } @@ -199,11 +198,10 @@ private function generateClassProperty(PropertyDefinition $definition) : Propert private function generateMethodParameter(PropertyDefinition $definition) : Param { -//ToDo: Remove return $this ->factory ->param($definition->getClassPropertyName()) - ->setType(str_replace('?', '', $this->getTypePhp($definition))); + ->setType($this->getTypePhp($definition)); } private function getAssignmentDefinition(string $name) : Assign @@ -250,12 +248,12 @@ private function generateSetter(PropertyDefinition $definition) : Method ->addParam($this->generateMethodParameter($definition)) ->addStmt($this->getAssignmentDefinition($definition->getClassPropertyName())) ->addStmt(new Return_(new Variable('this'))); -//ToDo: remove + if ($this->fullDocs || $definition->isArray()) { $blocks = [ sprintf( '@param %s $%s', - str_replace('|null', '', $this->getTypeDocBlock($definition)), + $this->getTypeDocBlock($definition), $definition->getClassPropertyName() ), ]; diff --git a/src/CodeGenerator/PhpParserGenerators/InterfaceCodeGenerator.php b/src/CodeGenerator/PhpParserGenerators/InterfaceCodeGenerator.php index 6141c29..fc50e8b 100644 --- a/src/CodeGenerator/PhpParserGenerators/InterfaceCodeGenerator.php +++ b/src/CodeGenerator/PhpParserGenerators/InterfaceCodeGenerator.php @@ -7,6 +7,7 @@ use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GeneratedFileDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GeneratedInterfaceDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ServiceInterfaceDefinition; +use function count; use function Safe\sprintf; class InterfaceCodeGenerator extends CodeGenerator @@ -31,26 +32,48 @@ public function generate(GeneratedInterfaceDefinition $definition) : GeneratedFi if ($definition instanceof ServiceInterfaceDefinition) { $methodBuilder = $this->factory->method($definition->getMethodName())->makePublic(); $request = $definition->getRequestType(); + $docBlocks = []; + if ($request !== null) { $this->use($fileBuilder, $definition->getNamespace(), $request); $methodBuilder->addParam( $this->factory->param('request')->setType($request->getClassName()) ); - //ToDo: Add full docblock support + if ($this->fullDocs) { + $docBlocks[] = sprintf( + '@param %s $%s', + $request->getClassName(), + 'request' + ); + } } $response = $definition->getResponseType(); if ($response !== null) { $this->use($fileBuilder, $definition->getNamespace(), $response); $methodBuilder->setReturnType($response->getClassName()); + if ($this->fullDocs) { + $docBlocks[] = sprintf( + '@return %s', + $response->getClassName() + ); + } } else { $methodBuilder->setReturnType('void'); } $description = $definition->getMethodDescription(); if ($description !== null) { - $methodBuilder->setDocComment($this->getDocComment([$description])); + if (count($docBlocks)) { + $docBlocks = [$description, '', ...$docBlocks]; + } else { + $docBlocks[] = $description; + } + } + + if (count($docBlocks) > 0) { + $methodBuilder->setDocComment($this->getDocComment($docBlocks)); } $interfaceBuilder->addStmt($methodBuilder); diff --git a/src/CodeGenerator/PhpParserGenerators/ServiceSubscriberCodeGenerator.php b/src/CodeGenerator/PhpParserGenerators/ServiceSubscriberCodeGenerator.php index 83f6e58..cad1a68 100644 --- a/src/CodeGenerator/PhpParserGenerators/ServiceSubscriberCodeGenerator.php +++ b/src/CodeGenerator/PhpParserGenerators/ServiceSubscriberCodeGenerator.php @@ -33,6 +33,7 @@ public function generate(GraphDefinition $graphDefinition) : GeneratedFileDefini $fileBuilder = $this->factory->namespace($subscriberDefinition->getNamespace()); $fileBuilder->addStmt($this->factory->use(ContainerInterface::class)); + $fileBuilder->addStmt($this->factory->use(RequestHandler::class)); $classBuilder = $this ->factory @@ -44,9 +45,6 @@ public function generate(GraphDefinition $graphDefinition) : GeneratedFileDefini $this->use($fileBuilder, $subscriberDefinition->getNamespace(), $implement); } - //ToDo: move upwards - $fileBuilder->addStmt($this->factory->use(RequestHandler::class)); - $services = []; foreach ($graphDefinition->getSpecifications() as $specification) { foreach ($specification->getOperations() as $operation) { @@ -55,85 +53,99 @@ public function generate(GraphDefinition $graphDefinition) : GeneratedFileDefini } } - //ToDo: full DocBlock support - $classBuilder->addStmts( - [ - $this - ->factory - ->property('locator') - ->makePrivate() - ->setType('ContainerInterface'), - $this - ->factory - ->method('__construct') - ->makePublic() - ->addParam( - $this->factory->param('locator')->setType('ContainerInterface') - ) - ->addStmt( - new Assign(new Variable('this->locator'), new Variable('locator')) - ), - $this - ->factory - ->method('getSubscribedServices') - ->makePublic() - ->makeStatic() - ->setDocComment('/** + $property = $this + ->factory + ->property('locator') + ->makePrivate() + ->setType('ContainerInterface'); + if ($this->fullDocs) { + $property->setDocComment('/** @var ContainerInterface */'); + } + + $constructor = $this + ->factory + ->method('__construct') + ->makePublic() + ->addParam( + $this->factory->param('locator')->setType('ContainerInterface') + ) + ->addStmt( + new Assign(new Variable('this->locator'), new Variable('locator')) + ); + if ($this->fullDocs) { + $constructor->setDocComment('/** @param ContainerInterface $locator */'); + } + + $getSubscribedServices = $this + ->factory + ->method('getSubscribedServices') + ->makePublic() + ->makeStatic() + ->setDocComment('/** * @inheritDoc */') - ->addStmt( - new Return_( - new Array_( - array_map( - static fn (string $service) : ArrayItem => - new ArrayItem( - new Concat( - new String_('?'), - new ClassConstFetch( - new Name($service), - 'class' - ) - ) - ), - $services + ->addStmt( + new Return_( + new Array_( + array_map( + static fn (string $service) : ArrayItem => + new ArrayItem( + new Concat( + new String_('?'), + new ClassConstFetch( + new Name($service), + 'class' ) ) - ) - ), - $this - ->factory - ->method('get') - ->makePublic() - ->setReturnType('?RequestHandler') - ->addParam( - $this->factory->param('interface')->setType('string') - ) - ->addStmt( - new If_(new BooleanNot(new MethodCall( - new Variable('this->locator'), - 'has', - [ - new Arg( - new Variable('interface') - ), - ] - )), ['stmts' => [new Return_($this->factory->val(null))]]) + ), + $services ) - ->addStmt( - new Return_( - new MethodCall( - new Variable('this->locator'), - 'get', - [ - new Arg( - new Variable('interface') - ), - ] - ) - ) + ) + ) + ); + + $getRequestHandler = $this + ->factory + ->method('get') + ->makePublic() + ->setReturnType('?RequestHandler') + ->addParam( + $this->factory->param('interface')->setType('string') + ) + ->addStmt( + new If_(new BooleanNot(new MethodCall( + new Variable('this->locator'), + 'has', + [ + new Arg( + new Variable('interface') ), - ] - ); + ] + )), ['stmts' => [new Return_($this->factory->val(null))]]) + ) + ->addStmt( + new Return_( + new MethodCall( + new Variable('this->locator'), + 'get', + [ + new Arg( + new Variable('interface') + ), + ] + ) + ) + ); + + if ($this->fullDocs) { + $docs = [ + '@param string $interface', + '@return RequestHandler|null', + ]; + $getRequestHandler->setDocComment($this->getDocComment($docs)); + } + + $classBuilder->addStmts([$property, $constructor, $getSubscribedServices, $getRequestHandler]); $fileBuilder->addStmt($classBuilder); diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 4a097f9..d3f2c21 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -22,6 +22,7 @@ public function getConfigTreeBuilder() ->scalarNode('root_name_space')->defaultValue('App\Generated')->cannotBeEmpty()->end() ->scalarNode('language_level')->defaultValue('7.4.0')->cannotBeEmpty()->end() ->scalarNode('generated_dir_permissions')->defaultValue('0755')->cannotBeEmpty()->end() + ->booleanNode('full_doc_blocks')->defaultValue(false)->end() ->arrayNode('specs') ->arrayPrototype() ->children() diff --git a/src/DependencyInjection/OpenApiServerExtension.php b/src/DependencyInjection/OpenApiServerExtension.php index 8cead3f..bac5822 100644 --- a/src/DependencyInjection/OpenApiServerExtension.php +++ b/src/DependencyInjection/OpenApiServerExtension.php @@ -32,6 +32,7 @@ public function load(array $configs, ContainerBuilder $container) : void * root_name_space:string, * language_level:string, * generated_dir_permissions: string, + * full_doc_blocks: bool, * specs: array{ * path: string, * type?: string, @@ -60,6 +61,7 @@ public function load(array $configs, ContainerBuilder $container) : void $container->setParameter('openapi.generated.code.root.namespace', $rootNameSpace); $container->setParameter('openapi.generated.code.language.level', $config['language_level']); $container->setParameter('openapi.generated.code.dir.permissions', $config['generated_dir_permissions']); + $container->setParameter('openapi.generated.code.full.doc.blocks', $config['full_doc_blocks']); $definition = $container->getDefinition(SpecificationLoader::class); diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index 84e0d86..7fcc846 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -31,12 +31,15 @@ %openapi.generated.code.language.level% + %openapi.generated.code.full.doc.blocks% %openapi.generated.code.language.level% + %openapi.generated.code.full.doc.blocks% %openapi.generated.code.language.level% + %openapi.generated.code.full.doc.blocks% %openapi.generated.code.dir.permissions%