[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 COMPOSER_VENDOR_NAME = "composerVendorName";
public static final String COMPOSER_PROJECT_NAME = "composerProjectName";
public static final String PHP_LEGACY_SUPPORT = "phpLegacySupport";
public static final Map<String, String> 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<String> typeHintable;
static {
SYMFONY_EXCEPTIONS = new HashMap<>();
@ -74,36 +78,44 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
embeddedTemplateDir = templateDir = "php-symfony";
setReservedWordsLowerCase(
Arrays.asList(
// local variables used in api methods (endpoints)
"resourcePath", "httpBody", "queryParams", "headerParams",
"formParams", "_header_accept", "_tempBody",
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")
// 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<String>(
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<String>(
Arrays.asList(
"\\DateTime",
"\\SplFileObject"
)
);
variableNamingConvention = "camelCase";
// provide primitives to mustache template
List sortedLanguageSpecificPrimitives= new ArrayList(languageSpecificPrimitives);
@ -124,7 +136,7 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
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 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(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 class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
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 class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
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 class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
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<String>(
Arrays.asList(
"array"
)
);
} else {
typeHintable = new HashSet<String>(
Arrays.asList(
"array",
"bool",
"float",
"int",
"string"
)
);
}
}
@Override
public Map<String, Object> postProcessOperations(Map<String, Object> objs) {
objs = super.postProcessOperations(objs);
Map<String, Object> operations = (Map<String, Object>) objs.get("operations");
operations.put("controllerName", toControllerName((String) operations.get("pathPrefix")));
operations.put("symfonyService", toSymfonyService((String) operations.get("pathPrefix")));
HashSet<CodegenSecurity> authMethods = new HashSet<>();
HashSet<String> imports = new HashSet<>();
List<CodegenOperation> operationList = (List<CodegenOperation>) 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 class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
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 class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
}
}
operations.put("imports", new ArrayList<>(imports));
operations.put("authMethods", authMethods);
return objs;
@ -397,7 +438,7 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
if (p instanceof MapProperty) {
MapProperty mp = (MapProperty) p;
Property inner = mp.getAdditionalProperties();
return getSwaggerType(p) + "array<string," + getTypeDeclaration(inner) + ">";
return getTypeDeclaration(inner) + "[]";
}
if (p instanceof RefProperty) {
@ -429,6 +470,21 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
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 @@ public class SymfonyServerCodegen extends AbstractPhpCodegen implements CodegenC
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}};
{{#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}}

View File

@ -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));

View File

@ -20,9 +20,8 @@
*/
namespace {{modelPackage}};
{{^isEnum}}
use \ArrayAccess;
{{^isEnum}}
{{#useStatements}}use {{this}};
{{/useStatements}}
{{/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}};

View File

@ -49,6 +49,7 @@ public class SymfonyServerOptionsProvider implements OptionsProvider {
.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();
}