Skip to content

Commit ef4f898

Browse files
authored
[PHP][php-symfony] Fix native enum default value expression in AbstractPhpCodegen (#23556)
* fix(php): emit native backed-enum defaults as Type::CASE AbstractPhpCodegen.toEnumDefaultValue used self::FQCN_CASE, which is invalid for PHP 8.1 enums when model properties use enum $ref with a sibling default. Emit datatype::caseName instead and document the upstream data flow. Extend optional-enum-query-ref-default.yaml with a pet-store CreatePetRequest example; add PhpSymfonyServerCodegenTest regression and refresh the issue draft. * fix(php): enum default uses short class + ::CASE under modelPackage Strip modelPackage from AbstractPhpCodegen.toEnumDefaultValue so model property defaults resolve in-file like type hints; keep native enum syntax.
1 parent eaeba8c commit ef4f898

File tree

3 files changed

+114
-4
lines changed

3 files changed

+114
-4
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -781,9 +781,46 @@ public String toEnumValue(String value, String datatype) {
781781
}
782782
}
783783

784+
/**
785+
* Builds the PHP expression for a backed enum case default (PHP 8.1+ {@code enum}).
786+
* <p>
787+
* The legacy {@code self::}{@code <datatype>_<CASE>} form came from class-constant style enums (#10273) and is
788+
* invalid when {@code datatype} is a namespaced class: {@code self::} only resolves constants on the current
789+
* class. Native enums must use {@code EnumType::CASE}.
790+
* <p>
791+
* Execution: {@code datatype} is produced upstream (e.g. {@link DefaultCodegen#updateCodegenPropertyEnum}) via
792+
* {@link #getTypeDeclaration(Schema)} for the referenced enum schema; {@code value} is the sanitized case name
793+
* from {@link #toEnumVarName}. When the enum class sits under {@link #modelPackage}, we emit only the short class
794+
* name plus {@code ::} so it matches sibling model references in generated files ({@code namespace} is
795+
* {@code modelPackage}; unqualified names resolve correctly). A fully qualified body without a leading
796+
* {@code \} would be resolved relative to the file namespace and is invalid PHP for defaults.
797+
*
798+
* @param value enum case name (e.g. {@code AVAILABLE})
799+
* @param datatype enum class as produced by {@link #getTypeDeclaration(Schema)} (may include {@code modelPackage})
800+
* @return PHP default expression for that case (e.g. {@code PetStatus::AVAILABLE})
801+
*/
784802
@Override
785803
public String toEnumDefaultValue(String value, String datatype) {
786-
return "self::" + datatype + "_" + value;
804+
return unqualifiedEnumClassForModelDefault(datatype) + "::" + value;
805+
}
806+
807+
/**
808+
* Strips {@link #modelPackage} from a declared enum class name so defaults use the same unqualified form as
809+
* property type hints in model templates.
810+
*
811+
* @param datatype enum class string from codegen (optional leading {@code \})
812+
* @return short class name if under {@code modelPackage}, otherwise the original {@code datatype}
813+
*/
814+
private String unqualifiedEnumClassForModelDefault(String datatype) {
815+
if (StringUtils.isBlank(datatype) || StringUtils.isBlank(modelPackage)) {
816+
return datatype;
817+
}
818+
String normalized = datatype.charAt(0) == '\\' ? datatype.substring(1) : datatype;
819+
String prefix = modelPackage + "\\";
820+
if (normalized.startsWith(prefix)) {
821+
return normalized.substring(prefix.length());
822+
}
823+
return datatype;
787824
}
788825

789826
@Override

modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpSymfonyServerCodegenTest.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717

1818
package org.openapitools.codegen.php;
1919

20+
import io.swagger.v3.oas.models.media.Schema;
2021
import org.openapitools.codegen.ClientOptInput;
2122
import org.openapitools.codegen.CodegenConstants;
23+
import org.openapitools.codegen.CodegenModel;
24+
import org.openapitools.codegen.CodegenProperty;
2225
import org.openapitools.codegen.DefaultGenerator;
2326
import org.openapitools.codegen.TestUtils;
2427
import org.openapitools.codegen.config.CodegenConfigurator;
@@ -398,6 +401,44 @@ public void testOptionalEnumRefQueryParameterWithDefaultAppliesOpenApiSemantics(
398401
output.deleteOnExit();
399402
}
400403

404+
/**
405+
* Model property: string enum {@code $ref} with sibling {@code default} must produce a valid PHP 8.1 backed-enum
406+
* default expression ({@code Type::CASE}), not {@code self::} + FQCN + {@code _CASE} from
407+
* {@link AbstractPhpCodegen#toEnumDefaultValue}.
408+
* <p>
409+
* Spec: {@code src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml} ({@code Pet.HTTP.CreatePetRequest.status}).
410+
*/
411+
@Test
412+
public void testModelPropertyEnumRefWithDefaultUsesNativeEnumCaseInCodegen() {
413+
final var openAPI = TestUtils.parseFlattenSpec(
414+
"src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml");
415+
final PhpSymfonyServerCodegen codegen = new PhpSymfonyServerCodegen();
416+
codegen.setOpenAPI(openAPI);
417+
codegen.processOpts();
418+
419+
Schema createPet = openAPI.getComponents().getSchemas().get("Pet.HTTP.CreatePetRequest");
420+
Assert.assertNotNull(createPet, "Fixture must define Pet.HTTP.CreatePetRequest");
421+
422+
CodegenModel cm = codegen.fromModel("Pet.HTTP.CreatePetRequest", createPet);
423+
codegen.postProcessModels(TestUtils.createCodegenModelWrapper(cm));
424+
425+
CodegenProperty status = cm.getVars().stream()
426+
.filter(v -> "status".equals(v.getName()))
427+
.findFirst()
428+
.orElseThrow(() -> new AssertionError("Expected status property on CreatePetRequest model"));
429+
430+
Assert.assertNotNull(
431+
status.getDefaultValue(),
432+
"Enum ref + OpenAPI default should set CodegenProperty.defaultValue (see AbstractPhpCodegen.toDefaultValue"
433+
+ " ref+default handling and updateCodegenPropertyEnum)");
434+
Assert.assertFalse(
435+
status.getDefaultValue().startsWith("self::"),
436+
"Invalid PHP: backed enum default must not use self:: prefix, got: " + status.getDefaultValue());
437+
Assert.assertTrue(
438+
status.getDefaultValue().contains("::AVAILABLE"),
439+
"Expected PetModelPetStatus::AVAILABLE (or FQCN::AVAILABLE), got: " + status.getDefaultValue());
440+
}
441+
401442
/**
402443
* Runs {@code php -l} on the file. Skips if {@code php} is not available (optional toolchain).
403444
*/

modules/openapi-generator/src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,32 @@
1-
# Minimal OpenAPI 3.1 spec: optional query with enum $ref + default (components.parameters).
2-
# Repro pattern: omitting the query key should behave like the declared default; php-symfony
3-
# may still validate null as enum type before business logic. See project troubleshooting doc.
1+
# Minimal OpenAPI 3.1 spec (pet store style):
2+
# - Optional query: enum $ref + default (components.parameters) — regression for query/controller path.
3+
# - Model property: enum $ref + sibling default on an object schema — invalid PHP may be emitted for the
4+
# property initializer (e.g. self::..._CASE) when default flows through AbstractPhpCodegen.toEnumDefaultValue.
45
openapi: 3.1.0
56
info:
67
title: Optional enum query ref with default (php-symfony repro)
78
version: '1.0'
9+
servers:
10+
- url: https://petstore.example.com/api
11+
# Explicit empty security for minimal test fixtures (no global auth).
12+
security: []
813
paths:
14+
/pets:
15+
post:
16+
summary: Create a pet (request body uses enum ref + default on a property)
17+
operationId: createPet
18+
requestBody:
19+
required: true
20+
content:
21+
application/json:
22+
schema:
23+
$ref: '#/components/schemas/Pet.HTTP.CreatePetRequest'
24+
responses:
25+
'201':
26+
description: Created
927
/pets/feed-hints:
1028
get:
29+
summary: List feed hints (optional enum query ref + default)
1130
operationId: listFeedHints
1231
parameters:
1332
- $ref: '#/components/parameters/ToneQuery'
@@ -35,6 +54,19 @@ components:
3554
$ref: '#/components/schemas/PetAnnouncementTone'
3655
default: friendly
3756
schemas:
57+
Pet.Model.PetStatus:
58+
type: string
59+
enum:
60+
- available
61+
- pending
62+
- sold
63+
Pet.HTTP.CreatePetRequest:
64+
type: object
65+
properties:
66+
# OAS 3.1: sibling keys beside $ref are allowed.
67+
status:
68+
$ref: '#/components/schemas/Pet.Model.PetStatus'
69+
default: available
3870
PetAnnouncementTone:
3971
type: string
4072
enum:

0 commit comments

Comments
 (0)