Skip to content

Commit

Permalink
[PHP][Symfony] Generate valid PHP code
Browse files Browse the repository at this point in the history
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);
  • Loading branch information
Nael Rashdeen committed Sep 27, 2017
1 parent 28a3206 commit 92a6ea3
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<>();
Expand Down Expand Up @@ -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<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);
Expand All @@ -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");
Expand All @@ -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() {
Expand All @@ -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));
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<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);
}
}

Expand All @@ -296,11 +340,9 @@ public Map<String, Object> postProcessOperations(Map<String, Object> 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("\\[\\]$", ""));
// }
}
}

Expand All @@ -310,7 +352,6 @@ public Map<String, Object> postProcessOperations(Map<String, Object> objs) {
}
}

operations.put("imports", new ArrayList<>(imports));
operations.put("authMethods", authMethods);

return objs;
Expand Down Expand Up @@ -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<string," + getTypeDeclaration(inner) + ">";
return getTypeDeclaration(inner) + "[]";
}

if (p instanceof RefProperty) {
return getTypeDeclaration(getPropertyTypeDeclaration(p));
}

return getPropertyTypeDeclaration(p);
}

Expand All @@ -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";
Expand All @@ -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()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

namespace {{apiPackage}};

{{#operations}}{{#imports}}use {{this}};
{{#operations}}{{#imports}}use {{import}};
{{/imports}}

/**
Expand Down Expand Up @@ -56,15 +56,15 @@ 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}}
{{#vendorExtensions.x-symfonyExceptionSimple}}
* @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}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}}

/**
Expand Down Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@
*/

namespace {{modelPackage}};
{{^isEnum}}

use \ArrayAccess;
{{^isEnum}}
{{#useStatements}}use {{this}};
{{/useStatements}}
{{/isEnum}}
Expand Down
Original file line number Diff line number Diff line change
@@ -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}};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public Map<String, String> 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();
}

Expand Down

0 comments on commit 92a6ea3

Please sign in to comment.