From 92a6ea3a1a53c4e75f513338bc79771f288f9ec5 Mon Sep 17 00:00:00 2001 From: Nael Rashdeen Date: Wed, 27 Sep 2017 13:51:51 +0200 Subject: [PATCH] [PHP][Symfony] Generate valid PHP code Fixes #5985 We were experiencing syntax issues when generating the Petstore example. See some of the examples below: StoreApiInterface.php namespace Swagger\Server\Api; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Swagger\Server\Model\Order; **use maparray<string,int>;** use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; UserApiInterface.php public function createUsersWithArrayInput(**User[]** $body); public function createUsersWithListInput(User[] $body); As far as I know, it is not possible to use array of objects in this way. PetApiInterface.php namespace Swagger\Server\Api; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Swagger\Server\Model\Pet; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Swagger\Server\Model\ApiResponse; **use string[];** use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; public function findPetsByStatus(string[] $status); public function findPetsByTags(string[] $tags); --- .../languages/SymfonyServerCodegen.java | 166 +++++++++++++----- .../main/resources/php-symfony/api.mustache | 6 +- .../php-symfony/api_controller.mustache | 6 +- .../main/resources/php-symfony/model.mustache | 3 +- .../php-symfony/model_generic.mustache | 2 +- .../options/SymfonyServerOptionsProvider.java | 1 + 6 files changed, 134 insertions(+), 50 deletions(-) diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/SymfonyServerCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/SymfonyServerCodegen.java index 05d4ba7ddd6..87ce66f16a7 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/SymfonyServerCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/SymfonyServerCodegen.java @@ -19,6 +19,7 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC public static final String BUNDLE_NAME = "bundleName"; public static final String COMPOSER_VENDOR_NAME = "composerVendorName"; public static final String COMPOSER_PROJECT_NAME = "composerProjectName"; + public static final String PHP_LEGACY_SUPPORT = "phpLegacySupport"; public static final Map SYMFONY_EXCEPTIONS; protected String testsPackage; protected String apiTestsPackage; @@ -32,6 +33,9 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC protected String bundleAlias; protected String controllerDirName = "Controller"; protected String controllerPackage; + protected Boolean phpLegacySupport = Boolean.TRUE; + + protected HashSet typeHintable; static { SYMFONY_EXCEPTIONS = new HashMap<>(); @@ -74,36 +78,44 @@ public SymfonyServerCodegen() { embeddedTemplateDir = templateDir = "php-symfony"; setReservedWordsLowerCase( - Arrays.asList( - // local variables used in api methods (endpoints) - "resourcePath", "httpBody", "queryParams", "headerParams", - "formParams", "_header_accept", "_tempBody", - - // PHP reserved words - "__halt_compiler", "abstract", "and", "array", "as", "break", "callable", "case", "catch", "class", "clone", "const", "continue", "declare", "default", "die", "do", "echo", "else", "elseif", "empty", "enddeclare", "endfor", "endforeach", "endif", "endswitch", "endwhile", "eval", "exit", "extends", "final", "for", "foreach", "function", "global", "goto", "if", "implements", "include", "include_once", "instanceof", "insteadof", "interface", "isset", "list", "namespace", "new", "or", "print", "private", "protected", "public", "require", "require_once", "return", "static", "switch", "throw", "trait", "try", "unset", "use", "var", "while", "xor") + Arrays.asList( + // local variables used in api methods (endpoints) + "resourcePath", "httpBody", "queryParams", "headerParams", + "formParams", "_header_accept", "_tempBody", + + // PHP reserved words + "__halt_compiler", "abstract", "and", "array", "as", "break", "callable", "case", "catch", "class", "clone", "const", "continue", "declare", "default", "die", "do", "echo", "else", "elseif", "empty", "enddeclare", "endfor", "endforeach", "endif", "endswitch", "endwhile", "eval", "exit", "extends", "final", "for", "foreach", "function", "global", "goto", "if", "implements", "include", "include_once", "instanceof", "insteadof", "interface", "isset", "list", "namespace", "new", "or", "print", "private", "protected", "public", "require", "require_once", "return", "static", "switch", "throw", "trait", "try", "unset", "use", "var", "while", "xor" + ) ); // ref: http://php.net/manual/en/language.types.intro.php languageSpecificPrimitives = new HashSet( - Arrays.asList( - "bool", - "boolean", - "int", - "integer", - "double", - "float", - "string", - "object", - "DateTime", - "mixed", - "number", - "void", - "byte") + Arrays.asList( + "bool", + "int", + "double", + "float", + "string", + "object", + "mixed", + "number", + "void", + "byte", + "array" + ) ); - instantiationTypes.put("array", "array"); - instantiationTypes.put("map", "map"); + //instantiationTypes.put("array", "array"); + //instantiationTypes.put("map", "map"); + + defaultIncludes = new HashSet( + Arrays.asList( + "\\DateTime", + "\\SplFileObject" + ) + ); + variableNamingConvention = "camelCase"; // provide primitives to mustache template List sortedLanguageSpecificPrimitives= new ArrayList(languageSpecificPrimitives); @@ -124,7 +136,7 @@ public SymfonyServerCodegen() { typeMapping.put("Date", "\\DateTime"); typeMapping.put("DateTime", "\\DateTime"); typeMapping.put("file", "\\SplFileObject"); - typeMapping.put("map", "map"); + typeMapping.put("map", "array"); typeMapping.put("array", "array"); typeMapping.put("list", "array"); typeMapping.put("object", "object"); @@ -137,6 +149,7 @@ public SymfonyServerCodegen() { cliOptions.add(new CliOption(COMPOSER_PROJECT_NAME, "The project name used in the composer package name. The template uses {{composerVendorName}}/{{composerProjectName}} for the composer package name. e.g. petstore-client. IMPORTANT NOTE (2016/03): composerProjectName will be deprecated and replaced by gitRepoId in the next swagger-codegen release")); cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "hides the timestamp when files were generated") .defaultValue(Boolean.TRUE.toString())); + cliOptions.add(new CliOption(PHP_LEGACY_SUPPORT, "Should the generated code be compatible with PHP 5.x?").defaultValue(Boolean.TRUE.toString())); } public String getBundleName() { @@ -150,6 +163,9 @@ public void setBundleName(String bundleName) { this.bundleAlias = snakeCase(bundleName).replaceAll("([A-Z]+)", "\\_$1").toLowerCase(); } + public void setPhpLegacySupport(Boolean support) { + this.phpLegacySupport = support; + } public String controllerFileFolder() { return (outputFolder + File.separator + toPackagePath(controllerPackage, srcBasePath)); @@ -218,6 +234,12 @@ public void processOpts() { additionalProperties.put(COMPOSER_VENDOR_NAME, composerVendorName); } + if (additionalProperties.containsKey(PHP_LEGACY_SUPPORT)) { + this.setPhpLegacySupport(Boolean.valueOf((String) additionalProperties.get(PHP_LEGACY_SUPPORT))); + } else { + additionalProperties.put(PHP_LEGACY_SUPPORT, phpLegacySupport); + } + additionalProperties.put("escapedInvokerPackage", invokerPackage.replace("\\", "\\\\")); additionalProperties.put("controllerPackage", controllerPackage); additionalProperties.put("apiTestsPackage", apiTestsPackage); @@ -264,26 +286,48 @@ public void processOpts() { supportingFiles.add(new SupportingFile(".travis.yml", getPackagePath(), ".travis.yml")); supportingFiles.add(new SupportingFile(".php_cs", getPackagePath(), ".php_cs")); supportingFiles.add(new SupportingFile("git_push.sh.mustache", getPackagePath(), "git_push.sh")); + + // Type-hintable primitive types + // ref: http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration + if (phpLegacySupport) { + typeHintable = new HashSet( + Arrays.asList( + "array" + ) + ); + } else { + typeHintable = new HashSet( + Arrays.asList( + "array", + "bool", + "float", + "int", + "string" + ) + ); + } } @Override public Map postProcessOperations(Map objs) { objs = super.postProcessOperations(objs); + Map operations = (Map) objs.get("operations"); operations.put("controllerName", toControllerName((String) operations.get("pathPrefix"))); operations.put("symfonyService", toSymfonyService((String) operations.get("pathPrefix"))); HashSet authMethods = new HashSet<>(); - HashSet imports = new HashSet<>(); List operationList = (List) operations.get("operation"); + for (CodegenOperation op : operationList) { + // Loop through all input parameters to determine, whether we have to import something to + // make the input type available. for (CodegenParameter param : op.allParams) { - final String simpleName = extractSimpleName(param.dataType); - param.vendorExtensions.put("x-simpleName", simpleName); - final boolean isScalarType = typeMapping.containsValue(param.dataType); - param.vendorExtensions.put("x-parameterType", isScalarType ? null : simpleName); - if (!isScalarType) { - imports.add(param.dataType); + // Determine if the paramter type is supported as a type hint and make it available + // to the templating engine + String typeHint = getTypeHint(param.dataType); + if (!typeHint.isEmpty()) { + param.vendorExtensions.put("x-parameterType", typeHint); } } @@ -296,11 +340,9 @@ public Map postProcessOperations(Map objs) { if (response.dataType != null) { final String dataType = extractSimpleName(response.dataType); response.vendorExtensions.put("x-simpleName", dataType); - imports.add(response.dataType.replaceFirst("\\[\\]$", "")); - } - - if (exception != null) { - imports.add(exception); + // if (!typeMapping.containsValue(dataType)) { + // imports.add(response.dataType.replaceFirst("\\[\\]$", "")); + // } } } @@ -310,7 +352,6 @@ public Map postProcessOperations(Map objs) { } } - operations.put("imports", new ArrayList<>(imports)); operations.put("authMethods", authMethods); return objs; @@ -397,13 +438,13 @@ public String getTypeDeclaration(Property p) { if (p instanceof MapProperty) { MapProperty mp = (MapProperty) p; Property inner = mp.getAdditionalProperties(); - return getSwaggerType(p) + "array"; + return getTypeDeclaration(inner) + "[]"; } - + if (p instanceof RefProperty) { return getTypeDeclaration(getPropertyTypeDeclaration(p)); } - + return getPropertyTypeDeclaration(p); } @@ -429,6 +470,21 @@ public String getTypeDeclaration(String name) { return super.getTypeDeclaration(name); } + /** + * Return the fully-qualified "Model" name for import + * + * @param name the name of the "Model" + * @return the fully-qualified "Model" name for import + */ + @Override + public String toModelImport(String name) { + if ("".equals(modelPackage())) { + return name; + } else { + return modelPackage() + "\\" + name; + } + } + public String toApiName(String name) { if (name.isEmpty()) { return "DefaultApiInterface"; @@ -451,4 +507,34 @@ protected String toSymfonyService(String name) { return prefix + name; } + + protected String getTypeHint(String type) { + // Type hint array types + if (type.endsWith("[]")) { + return "array"; + } + + // Check if the type is a native type that is type hintable in PHP + if (typeHintable.contains(type)) { + return type; + } + + // Default includes are referenced by their fully-qualified class name (including namespace) + if (defaultIncludes.contains(type)) { + return type; + } + + // Model classes are assumed to be imported and we reference them by their class name + if (isModelClass(type)) { + // This parameter is an instance of a model + return extractSimpleName(type); + } + + // PHP does not support type hinting for this parameter data type + return ""; + } + + protected Boolean isModelClass(String type) { + return Boolean.valueOf(type.contains(modelPackage())); + } } diff --git a/modules/swagger-codegen/src/main/resources/php-symfony/api.mustache b/modules/swagger-codegen/src/main/resources/php-symfony/api.mustache index 0caef2cfc6d..b3985a9489a 100644 --- a/modules/swagger-codegen/src/main/resources/php-symfony/api.mustache +++ b/modules/swagger-codegen/src/main/resources/php-symfony/api.mustache @@ -18,7 +18,7 @@ namespace {{apiPackage}}; -{{#operations}}{{#imports}}use {{this}}; +{{#operations}}{{#imports}}use {{import}}; {{/imports}} /** @@ -56,7 +56,7 @@ interface {{classname}} * {{/description}} {{#allParams}} - * @param {{vendorExtensions.x-simpleName}} ${{paramName}} {{description}} {{#required}}(required){{/required}}{{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + * @param {{dataType}} ${{paramName}} {{description}} {{#required}}(required){{/required}}{{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} {{/allParams}} * {{#responses}} @@ -64,7 +64,7 @@ interface {{classname}} * @throws {{vendorExtensions.x-symfonyExceptionSimple}} {{message}} {{/vendorExtensions.x-symfonyExceptionSimple}} {{^vendorExtensions.x-symfonyExceptionSimple}} - * @return {{^dataType}}void{{/dataType}}{{#dataType}}{{vendorExtensions.x-simpleName}}{{/dataType}} {{message}} + * @return {{^dataType}}void{{/dataType}}{{#dataType}}{{dataType}}{{/dataType}} {{message}} * {{/vendorExtensions.x-symfonyExceptionSimple}} {{/responses}} diff --git a/modules/swagger-codegen/src/main/resources/php-symfony/api_controller.mustache b/modules/swagger-codegen/src/main/resources/php-symfony/api_controller.mustache index c78a507203e..8fb9efb5ed7 100644 --- a/modules/swagger-codegen/src/main/resources/php-symfony/api_controller.mustache +++ b/modules/swagger-codegen/src/main/resources/php-symfony/api_controller.mustache @@ -24,7 +24,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\HttpException; use {{apiPackage}}\{{classname}}; -{{#imports}}use {{this}}; +{{#imports}}use {{import}}; {{/imports}} /** @@ -179,11 +179,9 @@ class {{controllerName}} extends Controller return new Response('', 204); {{/returnType}} {{#responses}} - {{#vendorExtensions.x-symfonyExceptionSimple}} - } catch ({{vendorExtensions.x-symfonyExceptionSimple}} $exception) { + } catch (HttpException $exception) { // {{message}} return $this->createErrorResponse($exception); - {{/vendorExtensions.x-symfonyExceptionSimple}} {{/responses}} } catch (Exception $fallthrough) { return $this->createErrorResponse(new HttpException(500, 'An unsuspected error occurred.', $fallthrough)); diff --git a/modules/swagger-codegen/src/main/resources/php-symfony/model.mustache b/modules/swagger-codegen/src/main/resources/php-symfony/model.mustache index 52b0934530d..6e865578075 100644 --- a/modules/swagger-codegen/src/main/resources/php-symfony/model.mustache +++ b/modules/swagger-codegen/src/main/resources/php-symfony/model.mustache @@ -20,9 +20,8 @@ */ namespace {{modelPackage}}; -{{^isEnum}} -use \ArrayAccess; +{{^isEnum}} {{#useStatements}}use {{this}}; {{/useStatements}} {{/isEnum}} diff --git a/modules/swagger-codegen/src/main/resources/php-symfony/model_generic.mustache b/modules/swagger-codegen/src/main/resources/php-symfony/model_generic.mustache index 165a96adc06..8e72f968406 100644 --- a/modules/swagger-codegen/src/main/resources/php-symfony/model_generic.mustache +++ b/modules/swagger-codegen/src/main/resources/php-symfony/model_generic.mustache @@ -1,4 +1,4 @@ -class {{classname}} {{#parentSchema}}extends {{{parent}}} {{/parentSchema}}implements ModelInterface, ArrayAccess +class {{classname}} {{#parentSchema}}extends {{{parent}}} {{/parentSchema}}implements ModelInterface, \ArrayAccess { const DISCRIMINATOR = {{#discriminator}}'{{discriminator}}'{{/discriminator}}{{^discriminator}}null{{/discriminator}}; diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/SymfonyServerOptionsProvider.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/SymfonyServerOptionsProvider.java index 4e975100f5e..ddb7d6ef387 100644 --- a/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/SymfonyServerOptionsProvider.java +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/SymfonyServerOptionsProvider.java @@ -49,6 +49,7 @@ public Map createOptions() { .put(CodegenConstants.ARTIFACT_VERSION, ARTIFACT_VERSION_VALUE) .put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "true") .put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, ALLOW_UNICODE_IDENTIFIERS_VALUE) + .put(SymfonyServerCodegen.PHP_LEGACY_SUPPORT, "true") .build(); }