Skip to content

Commit 1fa1b8c

Browse files
committed
fix(php): safe OpenAPI default literals for $ref schemas; tighten php-symfony tests
AbstractPhpCodegen maps getDefault() to valid PHP via defaultValueToPhpLiteral instead of blind toString(). PhpSymfonyServerCodegenTest asserts non-nullable handler params when an enum $ref has an OpenAPI default (petstore + optional spec).
1 parent e3a2ff0 commit 1fa1b8c

2 files changed

Lines changed: 68 additions & 11 deletions

File tree

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

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.slf4j.LoggerFactory;
3434

3535
import java.io.File;
36+
import java.math.BigDecimal;
3637
import java.util.*;
3738
import java.util.regex.Matcher;
3839
import java.util.regex.Pattern;
@@ -640,16 +641,47 @@ public String toDefaultValue(Schema p) {
640641
// references an enum model). That wrapper is often not classified as string/number here, but still carries
641642
// the default OpenAPI value — needed so Mustache can emit `query->get(..., <default>)` for php-symfony.
642643
if (p.getDefault() != null) {
643-
Object def = p.getDefault();
644-
if (def instanceof String) {
645-
return "'" + escapeTextInSingleQuotes((String) def) + "'";
646-
}
647-
return def.toString();
644+
return defaultValueToPhpLiteral(p.getDefault());
648645
}
649646

650647
return null;
651648
}
652649

650+
/**
651+
* Converts a JSON Schema {@code default} value to a PHP expression suitable for templates (e.g. second argument to
652+
* {@code query->get}). Only safe scalar literals are supported; unknown types log a warning and yield {@code null}
653+
* so we do not emit broken PHP from {@code Object#toString()}.
654+
*/
655+
private String defaultValueToPhpLiteral(Object def) {
656+
if (def == null) {
657+
return null;
658+
}
659+
if (def instanceof String) {
660+
return "'" + escapeTextInSingleQuotes((String) def) + "'";
661+
}
662+
if (def instanceof Boolean) {
663+
return Boolean.TRUE.equals(def) ? "true" : "false";
664+
}
665+
if (def instanceof BigDecimal) {
666+
return ((BigDecimal) def).toPlainString();
667+
}
668+
if (def instanceof Number) {
669+
String s = def.toString();
670+
if (s.contains("Infinity") || s.contains("NaN")) {
671+
LOGGER.warn("Unsupported numeric default for PHP literal: {}", def);
672+
return null;
673+
}
674+
return s;
675+
}
676+
if (def instanceof Character) {
677+
return "'" + escapeTextInSingleQuotes(String.valueOf((Character) def)) + "'";
678+
}
679+
LOGGER.warn(
680+
"Cannot convert OpenAPI default of type {} to a PHP literal; omitting defaultValue",
681+
def.getClass().getName());
682+
return null;
683+
}
684+
653685
@Override
654686
public void setParameterExampleValue(CodegenParameter p) {
655687
String example;

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

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,14 +216,20 @@ public void testPetstoreDottedEnumRefQueryParameterUsesShortClassInApiInterface(
216216
Assert.assertTrue(
217217
apiContent.contains("use Org\\OpenAPITools\\Petstore\\Model\\PetModelPetStatus;"),
218218
"Expected enum model import");
219-
// Optional enum ref may carry an OpenAPI default: php-symfony api.mustache omits leading "?" / "|null" when
220-
// defaultValue is set (handler always receives the enum after the controller applies the default).
219+
// This spec sets default: available on the enum $ref; the handler must be non-nullable (no leading "?" /
220+
// "|null") because the controller always supplies a value after applying the OpenAPI default.
221221
Assert.assertTrue(
222-
Pattern.compile("public function listPets\\(\\s*\\??PetModelPetStatus\\s+\\$status,").matcher(apiContent).find(),
223-
"Expected enum ref query param to use short class in type hint");
222+
Pattern.compile("public function listPets\\(\\s*PetModelPetStatus\\s+\\$status,").matcher(apiContent).find(),
223+
"Expected defaulted enum-ref query param to use short non-nullable class in type hint");
224+
Assert.assertFalse(
225+
Pattern.compile("public function listPets\\(\\s*\\?PetModelPetStatus\\s+\\$status").matcher(apiContent).find(),
226+
"Defaulted enum-ref query param must not use nullable type hint (?PetModelPetStatus)");
224227
Assert.assertTrue(
225-
Pattern.compile("@param\\s+PetModelPetStatus(\\|null)?\\s+\\$status\\b").matcher(apiContent).find(),
226-
"PHPDoc @param should use short PetModelPetStatus (optional |null when no default in spec)");
228+
Pattern.compile("@param\\s+PetModelPetStatus\\s+\\$status\\b").matcher(apiContent).find(),
229+
"PHPDoc @param should use short PetModelPetStatus without |null when OpenAPI default is set");
230+
Assert.assertFalse(
231+
Pattern.compile("@param\\s+PetModelPetStatus\\|null\\s+\\$status\\b").matcher(apiContent).find(),
232+
"PHPDoc must not document |null for enum ref when OpenAPI default is set");
227233
Assert.assertFalse(
228234
apiContent.contains("?\\Org\\OpenAPITools\\Petstore\\Model\\PetModelPetStatus $status"),
229235
"Signature must not use leading-backslash FQCN when a matching use import exists");
@@ -275,6 +281,25 @@ public void testOptionalEnumRefQueryParameterWithDefaultAppliesOpenApiSemantics(
275281
DefaultGenerator generator = new DefaultGenerator();
276282
List<File> files = generator.opts(clientOptInput).generate();
277283

284+
File apiInterfaceFile = files.stream()
285+
.filter(f -> "DefaultApiInterface.php".equals(f.getName()) && f.getPath().contains("Api" + File.separator))
286+
.findFirst()
287+
.orElseThrow(() -> new AssertionError("DefaultApiInterface.php not generated"));
288+
String apiContent = Files.readString(apiInterfaceFile.toPath(), StandardCharsets.UTF_8);
289+
Assert.assertTrue(
290+
Pattern.compile("public function listFeedHints\\(\\s*PetAnnouncementTone\\s+\\$tone,").matcher(apiContent).find(),
291+
"Expected defaulted enum-ref query param to use short non-nullable class in API interface type hint");
292+
Assert.assertFalse(
293+
Pattern.compile("public function listFeedHints\\(\\s*\\?PetAnnouncementTone\\s+\\$tone").matcher(apiContent).find(),
294+
"Defaulted enum-ref query param must not use nullable type hint (?PetAnnouncementTone)");
295+
Assert.assertTrue(
296+
Pattern.compile("@param\\s+PetAnnouncementTone\\s+\\$tone\\b").matcher(apiContent).find(),
297+
"PHPDoc @param should use PetAnnouncementTone without |null when OpenAPI default is set");
298+
Assert.assertFalse(
299+
Pattern.compile("@param\\s+PetAnnouncementTone\\|null\\s+\\$tone\\b").matcher(apiContent).find(),
300+
"PHPDoc must not document |null for enum ref when OpenAPI default is set");
301+
assertGeneratedPhpSyntaxValid(apiInterfaceFile);
302+
278303
File controllerFile = files.stream()
279304
.filter(f -> "DefaultController.php".equals(f.getName()) && f.getPath().contains("Controller" + File.separator))
280305
.findFirst()

0 commit comments

Comments
 (0)