[PHP][Symfony] Generate valid PHP code (#6578)

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);
This commit is contained in:
naelrashdeen 2017-10-12 11:40:11 +02:00 committed by wing328
parent b716b378c4
commit 5ffdee4479
6 changed files with 133 additions and 49 deletions

View File

@ -19,6 +19,7 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
public static final String BUNDLE_NAME = "bundleName"; public static final String BUNDLE_NAME = "bundleName";
public static final String COMPOSER_VENDOR_NAME = "composerVendorName"; public static final String COMPOSER_VENDOR_NAME = "composerVendorName";
public static final String COMPOSER_PROJECT_NAME = "composerProjectName"; public static final String COMPOSER_PROJECT_NAME = "composerProjectName";
public static final String PHP_LEGACY_SUPPORT = "phpLegacySupport";
public static final Map<String, String> SYMFONY_EXCEPTIONS; public static final Map<String, String> SYMFONY_EXCEPTIONS;
protected String testsPackage; protected String testsPackage;
protected String apiTestsPackage; protected String apiTestsPackage;
@ -32,6 +33,9 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
protected String bundleAlias; protected String bundleAlias;
protected String controllerDirName = "Controller"; protected String controllerDirName = "Controller";
protected String controllerPackage; protected String controllerPackage;
protected Boolean phpLegacySupport = Boolean.TRUE;
protected HashSet<String> typeHintable;
static { static {
SYMFONY_EXCEPTIONS = new HashMap<>(); SYMFONY_EXCEPTIONS = new HashMap<>();
@ -74,36 +78,44 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
embeddedTemplateDir = templateDir = "php-symfony"; embeddedTemplateDir = templateDir = "php-symfony";
setReservedWordsLowerCase( setReservedWordsLowerCase(
Arrays.asList( Arrays.asList(
// local variables used in api methods (endpoints) // local variables used in api methods (endpoints)
"resourcePath", "httpBody", "queryParams", "headerParams", "resourcePath", "httpBody", "queryParams", "headerParams",
"formParams", "_header_accept", "_tempBody", "formParams", "_header_accept", "_tempBody",
// PHP reserved words // 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") "__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 // ref: http://php.net/manual/en/language.types.intro.php
languageSpecificPrimitives = new HashSet<String>( languageSpecificPrimitives = new HashSet<String>(
Arrays.asList( Arrays.asList(
"bool", "bool",
"boolean", "int",
"int", "double",
"integer", "float",
"double", "string",
"float", "object",
"string", "mixed",
"object", "number",
"DateTime", "void",
"mixed", "byte",
"number", "array"
"void", )
"byte")
); );
instantiationTypes.put("array", "array"); //instantiationTypes.put("array", "array");
instantiationTypes.put("map", "map"); //instantiationTypes.put("map", "map");
defaultIncludes = new HashSet<String>(
Arrays.asList(
"\\DateTime",
"\\SplFileObject"
)
);
variableNamingConvention = "camelCase";
// provide primitives to mustache template // provide primitives to mustache template
List sortedLanguageSpecificPrimitives= new ArrayList(languageSpecificPrimitives); List sortedLanguageSpecificPrimitives= new ArrayList(languageSpecificPrimitives);
@ -124,7 +136,7 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
typeMapping.put("Date", "\\DateTime"); typeMapping.put("Date", "\\DateTime");
typeMapping.put("DateTime", "\\DateTime"); typeMapping.put("DateTime", "\\DateTime");
typeMapping.put("file", "\\SplFileObject"); typeMapping.put("file", "\\SplFileObject");
typeMapping.put("map", "map"); typeMapping.put("map", "array");
typeMapping.put("array", "array"); typeMapping.put("array", "array");
typeMapping.put("list", "array"); typeMapping.put("list", "array");
typeMapping.put("object", "object"); typeMapping.put("object", "object");
@ -137,6 +149,7 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
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(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") cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "hides the timestamp when files were generated")
.defaultValue(Boolean.TRUE.toString())); .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() { public String getBundleName() {
@ -150,6 +163,9 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
this.bundleAlias = snakeCase(bundleName).replaceAll("([A-Z]+)", "\\_$1").toLowerCase(); this.bundleAlias = snakeCase(bundleName).replaceAll("([A-Z]+)", "\\_$1").toLowerCase();
} }
public void setPhpLegacySupport(Boolean support) {
this.phpLegacySupport = support;
}
public String controllerFileFolder() { public String controllerFileFolder() {
return (outputFolder + File.separator + toPackagePath(controllerPackage, srcBasePath)); return (outputFolder + File.separator + toPackagePath(controllerPackage, srcBasePath));
@ -218,6 +234,12 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
additionalProperties.put(COMPOSER_VENDOR_NAME, composerVendorName); 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("escapedInvokerPackage", invokerPackage.replace("\\", "\\\\"));
additionalProperties.put("controllerPackage", controllerPackage); additionalProperties.put("controllerPackage", controllerPackage);
additionalProperties.put("apiTestsPackage", apiTestsPackage); additionalProperties.put("apiTestsPackage", apiTestsPackage);
@ -264,26 +286,48 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
supportingFiles.add(new SupportingFile(".travis.yml", getPackagePath(), ".travis.yml")); supportingFiles.add(new SupportingFile(".travis.yml", getPackagePath(), ".travis.yml"));
supportingFiles.add(new SupportingFile(".php_cs", getPackagePath(), ".php_cs")); supportingFiles.add(new SupportingFile(".php_cs", getPackagePath(), ".php_cs"));
supportingFiles.add(new SupportingFile("git_push.sh.mustache", getPackagePath(), "git_push.sh")); 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<String>(
Arrays.asList(
"array"
)
);
} else {
typeHintable = new HashSet<String>(
Arrays.asList(
"array",
"bool",
"float",
"int",
"string"
)
);
}
} }
@Override @Override
public Map<String, Object> postProcessOperations(Map<String, Object> objs) { public Map<String, Object> postProcessOperations(Map<String, Object> objs) {
objs = super.postProcessOperations(objs); objs = super.postProcessOperations(objs);
Map<String, Object> operations = (Map<String, Object>) objs.get("operations"); Map<String, Object> operations = (Map<String, Object>) objs.get("operations");
operations.put("controllerName", toControllerName((String) operations.get("pathPrefix"))); operations.put("controllerName", toControllerName((String) operations.get("pathPrefix")));
operations.put("symfonyService", toSymfonyService((String) operations.get("pathPrefix"))); operations.put("symfonyService", toSymfonyService((String) operations.get("pathPrefix")));
HashSet<CodegenSecurity> authMethods = new HashSet<>(); HashSet<CodegenSecurity> authMethods = new HashSet<>();
HashSet<String> imports = new HashSet<>();
List<CodegenOperation> operationList = (List<CodegenOperation>) operations.get("operation"); List<CodegenOperation> operationList = (List<CodegenOperation>) operations.get("operation");
for (CodegenOperation op : operationList) { 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) { for (CodegenParameter param : op.allParams) {
final String simpleName = extractSimpleName(param.dataType); // Determine if the paramter type is supported as a type hint and make it available
param.vendorExtensions.put("x-simpleName", simpleName); // to the templating engine
final boolean isScalarType = typeMapping.containsValue(param.dataType); String typeHint = getTypeHint(param.dataType);
param.vendorExtensions.put("x-parameterType", isScalarType ? null : simpleName); if (!typeHint.isEmpty()) {
if (!isScalarType) { param.vendorExtensions.put("x-parameterType", typeHint);
imports.add(param.dataType);
} }
} }
@ -296,11 +340,9 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
if (response.dataType != null) { if (response.dataType != null) {
final String dataType = extractSimpleName(response.dataType); final String dataType = extractSimpleName(response.dataType);
response.vendorExtensions.put("x-simpleName", dataType); response.vendorExtensions.put("x-simpleName", dataType);
imports.add(response.dataType.replaceFirst("\\[\\]$", "")); // if (!typeMapping.containsValue(dataType)) {
} // imports.add(response.dataType.replaceFirst("\\[\\]$", ""));
// }
if (exception != null) {
imports.add(exception);
} }
} }
@ -310,7 +352,6 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
} }
} }
operations.put("imports", new ArrayList<>(imports));
operations.put("authMethods", authMethods); operations.put("authMethods", authMethods);
return objs; return objs;
@ -397,7 +438,7 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
if (p instanceof MapProperty) { if (p instanceof MapProperty) {
MapProperty mp = (MapProperty) p; MapProperty mp = (MapProperty) p;
Property inner = mp.getAdditionalProperties(); Property inner = mp.getAdditionalProperties();
return getSwaggerType(p) + "array<string," + getTypeDeclaration(inner) + ">"; return getTypeDeclaration(inner) + "[]";
} }
if (p instanceof RefProperty) { if (p instanceof RefProperty) {
@ -429,6 +470,21 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
return super.getTypeDeclaration(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) { public String toApiName(String name) {
if (name.isEmpty()) { if (name.isEmpty()) {
return "DefaultApiInterface"; return "DefaultApiInterface";
@ -451,4 +507,34 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
return prefix + 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()));
}
} }

View File

@ -18,7 +18,7 @@
namespace {{apiPackage}}; namespace {{apiPackage}};
{{#operations}}{{#imports}}use {{this}}; {{#operations}}{{#imports}}use {{import}};
{{/imports}} {{/imports}}
/** /**
@ -56,7 +56,7 @@ interface {{classname}}
* *
{{/description}} {{/description}}
{{#allParams}} {{#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}} {{/allParams}}
* *
{{#responses}} {{#responses}}
@ -64,7 +64,7 @@ interface {{classname}}
* @throws {{vendorExtensions.x-symfonyExceptionSimple}} {{message}} * @throws {{vendorExtensions.x-symfonyExceptionSimple}} {{message}}
{{/vendorExtensions.x-symfonyExceptionSimple}} {{/vendorExtensions.x-symfonyExceptionSimple}}
{{^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}} {{/vendorExtensions.x-symfonyExceptionSimple}}
{{/responses}} {{/responses}}

View File

@ -24,7 +24,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use {{apiPackage}}\{{classname}}; use {{apiPackage}}\{{classname}};
{{#imports}}use {{this}}; {{#imports}}use {{import}};
{{/imports}} {{/imports}}
/** /**
@ -179,11 +179,9 @@ class {{controllerName}} extends Controller
return new Response('', 204); return new Response('', 204);
{{/returnType}} {{/returnType}}
{{#responses}} {{#responses}}
{{#vendorExtensions.x-symfonyExceptionSimple}} } catch (HttpException $exception) {
} catch ({{vendorExtensions.x-symfonyExceptionSimple}} $exception) {
// {{message}} // {{message}}
return $this->createErrorResponse($exception); return $this->createErrorResponse($exception);
{{/vendorExtensions.x-symfonyExceptionSimple}}
{{/responses}} {{/responses}}
} catch (Exception $fallthrough) { } catch (Exception $fallthrough) {
return $this->createErrorResponse(new HttpException(500, 'An unsuspected error occurred.', $fallthrough)); return $this->createErrorResponse(new HttpException(500, 'An unsuspected error occurred.', $fallthrough));

View File

@ -20,9 +20,8 @@
*/ */
namespace {{modelPackage}}; namespace {{modelPackage}};
{{^isEnum}}
use \ArrayAccess; {{^isEnum}}
{{#useStatements}}use {{this}}; {{#useStatements}}use {{this}};
{{/useStatements}} {{/useStatements}}
{{/isEnum}} {{/isEnum}}

View File

@ -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}}; const DISCRIMINATOR = {{#discriminator}}'{{discriminator}}'{{/discriminator}}{{^discriminator}}null{{/discriminator}};

View File

@ -49,6 +49,7 @@ public class SymfonyServerOptionsProvider implements OptionsProvider {
.put(CodegenConstants.ARTIFACT_VERSION, ARTIFACT_VERSION_VALUE) .put(CodegenConstants.ARTIFACT_VERSION, ARTIFACT_VERSION_VALUE)
.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "true") .put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "true")
.put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, ALLOW_UNICODE_IDENTIFIERS_VALUE) .put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, ALLOW_UNICODE_IDENTIFIERS_VALUE)
.put(SymfonyServerCodegen.PHP_LEGACY_SUPPORT, "true")
.build(); .build();
} }