Skip to content

Commit f13c25e

Browse files
committed
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.
1 parent a053922 commit f13c25e

3 files changed

Lines changed: 93 additions & 4 deletions

File tree

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -781,9 +781,25 @@ 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}. We concatenate with {@code ::} so templates emit valid PHP (e.g.
794+
* {@code \Vendor\Model\Status::AVAILABLE}).
795+
*
796+
* @param value enum case name (e.g. {@code AVAILABLE})
797+
* @param datatype enum class as in generated PHP (often leading {@code \} + FQCN)
798+
* @return PHP default expression for that case
799+
*/
784800
@Override
785801
public String toEnumDefaultValue(String value, String datatype) {
786-
return "self::" + datatype + "_" + value;
802+
return datatype + "::" + value;
787803
}
788804

789805
@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;
@@ -328,6 +331,44 @@ public void testOptionalEnumRefQueryParameterWithDefaultAppliesOpenApiSemantics(
328331
output.deleteOnExit();
329332
}
330333

334+
/**
335+
* Model property: string enum {@code $ref} with sibling {@code default} must produce a valid PHP 8.1 backed-enum
336+
* default expression ({@code Type::CASE}), not {@code self::} + FQCN + {@code _CASE} from
337+
* {@link AbstractPhpCodegen#toEnumDefaultValue}.
338+
* <p>
339+
* Spec: {@code src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml} ({@code Pet.HTTP.CreatePetRequest.status}).
340+
*/
341+
@Test
342+
public void testModelPropertyEnumRefWithDefaultUsesNativeEnumCaseInCodegen() {
343+
final var openAPI = TestUtils.parseFlattenSpec(
344+
"src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml");
345+
final PhpSymfonyServerCodegen codegen = new PhpSymfonyServerCodegen();
346+
codegen.setOpenAPI(openAPI);
347+
codegen.processOpts();
348+
349+
Schema createPet = openAPI.getComponents().getSchemas().get("Pet.HTTP.CreatePetRequest");
350+
Assert.assertNotNull(createPet, "Fixture must define Pet.HTTP.CreatePetRequest");
351+
352+
CodegenModel cm = codegen.fromModel("Pet.HTTP.CreatePetRequest", createPet);
353+
codegen.postProcessModels(TestUtils.createCodegenModelWrapper(cm));
354+
355+
CodegenProperty status = cm.getVars().stream()
356+
.filter(v -> "status".equals(v.getName()))
357+
.findFirst()
358+
.orElseThrow(() -> new AssertionError("Expected status property on CreatePetRequest model"));
359+
360+
Assert.assertNotNull(
361+
status.getDefaultValue(),
362+
"Enum ref + OpenAPI default should set CodegenProperty.defaultValue (see AbstractPhpCodegen.toDefaultValue"
363+
+ " ref+default handling and updateCodegenPropertyEnum)");
364+
Assert.assertFalse(
365+
status.getDefaultValue().startsWith("self::"),
366+
"Invalid PHP: backed enum default must not use self:: prefix, got: " + status.getDefaultValue());
367+
Assert.assertTrue(
368+
status.getDefaultValue().contains("::AVAILABLE"),
369+
"Expected PetModelPetStatus::AVAILABLE (or FQCN::AVAILABLE), got: " + status.getDefaultValue());
370+
}
371+
331372
/**
332373
* Runs {@code php -l} on the file. Skips if {@code php} is not available (optional toolchain).
333374
*/

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)