diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java index 0a1e1de1a194..4315d09c8098 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java @@ -781,9 +781,46 @@ public String toEnumValue(String value, String datatype) { } } + /** + * Builds the PHP expression for a backed enum case default (PHP 8.1+ {@code enum}). + *
+ * The legacy {@code self::}{@code
+ * Execution: {@code datatype} is produced upstream (e.g. {@link DefaultCodegen#updateCodegenPropertyEnum}) via
+ * {@link #getTypeDeclaration(Schema)} for the referenced enum schema; {@code value} is the sanitized case name
+ * from {@link #toEnumVarName}. When the enum class sits under {@link #modelPackage}, we emit only the short class
+ * name plus {@code ::} so it matches sibling model references in generated files ({@code namespace} is
+ * {@code modelPackage}; unqualified names resolve correctly). A fully qualified body without a leading
+ * {@code \} would be resolved relative to the file namespace and is invalid PHP for defaults.
+ *
+ * @param value enum case name (e.g. {@code AVAILABLE})
+ * @param datatype enum class as produced by {@link #getTypeDeclaration(Schema)} (may include {@code modelPackage})
+ * @return PHP default expression for that case (e.g. {@code PetStatus::AVAILABLE})
+ */
@Override
public String toEnumDefaultValue(String value, String datatype) {
- return "self::" + datatype + "_" + value;
+ return unqualifiedEnumClassForModelDefault(datatype) + "::" + value;
+ }
+
+ /**
+ * Strips {@link #modelPackage} from a declared enum class name so defaults use the same unqualified form as
+ * property type hints in model templates.
+ *
+ * @param datatype enum class string from codegen (optional leading {@code \})
+ * @return short class name if under {@code modelPackage}, otherwise the original {@code datatype}
+ */
+ private String unqualifiedEnumClassForModelDefault(String datatype) {
+ if (StringUtils.isBlank(datatype) || StringUtils.isBlank(modelPackage)) {
+ return datatype;
+ }
+ String normalized = datatype.charAt(0) == '\\' ? datatype.substring(1) : datatype;
+ String prefix = modelPackage + "\\";
+ if (normalized.startsWith(prefix)) {
+ return normalized.substring(prefix.length());
+ }
+ return datatype;
}
@Override
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpSymfonyServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpSymfonyServerCodegenTest.java
index 291d25da5ca5..f2ca5c84da98 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpSymfonyServerCodegenTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpSymfonyServerCodegenTest.java
@@ -17,8 +17,11 @@
package org.openapitools.codegen.php;
+import io.swagger.v3.oas.models.media.Schema;
import org.openapitools.codegen.ClientOptInput;
import org.openapitools.codegen.CodegenConstants;
+import org.openapitools.codegen.CodegenModel;
+import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.config.CodegenConfigurator;
@@ -328,6 +331,44 @@ public void testOptionalEnumRefQueryParameterWithDefaultAppliesOpenApiSemantics(
output.deleteOnExit();
}
+ /**
+ * Model property: string enum {@code $ref} with sibling {@code default} must produce a valid PHP 8.1 backed-enum
+ * default expression ({@code Type::CASE}), not {@code self::} + FQCN + {@code _CASE} from
+ * {@link AbstractPhpCodegen#toEnumDefaultValue}.
+ *
+ * Spec: {@code src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml} ({@code Pet.HTTP.CreatePetRequest.status}).
+ */
+ @Test
+ public void testModelPropertyEnumRefWithDefaultUsesNativeEnumCaseInCodegen() {
+ final var openAPI = TestUtils.parseFlattenSpec(
+ "src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml");
+ final PhpSymfonyServerCodegen codegen = new PhpSymfonyServerCodegen();
+ codegen.setOpenAPI(openAPI);
+ codegen.processOpts();
+
+ Schema createPet = openAPI.getComponents().getSchemas().get("Pet.HTTP.CreatePetRequest");
+ Assert.assertNotNull(createPet, "Fixture must define Pet.HTTP.CreatePetRequest");
+
+ CodegenModel cm = codegen.fromModel("Pet.HTTP.CreatePetRequest", createPet);
+ codegen.postProcessModels(TestUtils.createCodegenModelWrapper(cm));
+
+ CodegenProperty status = cm.getVars().stream()
+ .filter(v -> "status".equals(v.getName()))
+ .findFirst()
+ .orElseThrow(() -> new AssertionError("Expected status property on CreatePetRequest model"));
+
+ Assert.assertNotNull(
+ status.getDefaultValue(),
+ "Enum ref + OpenAPI default should set CodegenProperty.defaultValue (see AbstractPhpCodegen.toDefaultValue"
+ + " ref+default handling and updateCodegenPropertyEnum)");
+ Assert.assertFalse(
+ status.getDefaultValue().startsWith("self::"),
+ "Invalid PHP: backed enum default must not use self:: prefix, got: " + status.getDefaultValue());
+ Assert.assertTrue(
+ status.getDefaultValue().contains("::AVAILABLE"),
+ "Expected PetModelPetStatus::AVAILABLE (or FQCN::AVAILABLE), got: " + status.getDefaultValue());
+ }
+
/**
* Runs {@code php -l} on the file. Skips if {@code php} is not available (optional toolchain).
*/
diff --git a/modules/openapi-generator/src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml b/modules/openapi-generator/src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml
index a1b1c0d55ea0..2fb394f57a3b 100644
--- a/modules/openapi-generator/src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml
+++ b/modules/openapi-generator/src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml
@@ -1,13 +1,32 @@
-# Minimal OpenAPI 3.1 spec: optional query with enum $ref + default (components.parameters).
-# Repro pattern: omitting the query key should behave like the declared default; php-symfony
-# may still validate null as enum type before business logic. See project troubleshooting doc.
+# Minimal OpenAPI 3.1 spec (pet store style):
+# - Optional query: enum $ref + default (components.parameters) — regression for query/controller path.
+# - Model property: enum $ref + sibling default on an object schema — invalid PHP may be emitted for the
+# property initializer (e.g. self::..._CASE) when default flows through AbstractPhpCodegen.toEnumDefaultValue.
openapi: 3.1.0
info:
title: Optional enum query ref with default (php-symfony repro)
version: '1.0'
+servers:
+ - url: https://petstore.example.com/api
+# Explicit empty security for minimal test fixtures (no global auth).
+security: []
paths:
+ /pets:
+ post:
+ summary: Create a pet (request body uses enum ref + default on a property)
+ operationId: createPet
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Pet.HTTP.CreatePetRequest'
+ responses:
+ '201':
+ description: Created
/pets/feed-hints:
get:
+ summary: List feed hints (optional enum query ref + default)
operationId: listFeedHints
parameters:
- $ref: '#/components/parameters/ToneQuery'
@@ -35,6 +54,19 @@ components:
$ref: '#/components/schemas/PetAnnouncementTone'
default: friendly
schemas:
+ Pet.Model.PetStatus:
+ type: string
+ enum:
+ - available
+ - pending
+ - sold
+ Pet.HTTP.CreatePetRequest:
+ type: object
+ properties:
+ # OAS 3.1: sibling keys beside $ref are allowed.
+ status:
+ $ref: '#/components/schemas/Pet.Model.PetStatus'
+ default: available
PetAnnouncementTone:
type: string
enum: