Skip to content

Commit e3a2ff0

Browse files
committed
fix(php): honor OpenAPI default beside $ref in toDefaultValue
Parameter/model schemas that combine $ref with a sibling default were not handled by the boolean/number/string branches, so CodegenParameter.defaultValue stayed empty and php-symfony emitted query->get without the second argument. Add a fallback: when getDefault() is set, emit a PHP literal (quoted strings via escapeTextInSingleQuotes, other types via toString()). Update PhpSymfonyServerCodegenTest: assert optional enum-ref query defaults, and relax petstore interface assertions when a default is present (non-nullable signature per api.mustache).
1 parent c07f3a0 commit e3a2ff0

3 files changed

Lines changed: 125 additions & 3 deletions

File tree

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,17 @@ public String toDefaultValue(Schema p) {
636636
}
637637
}
638638

639+
// OAS 3.x: `default` may appear alongside `$ref` on the same schema (e.g. optional query param whose schema
640+
// references an enum model). That wrapper is often not classified as string/number here, but still carries
641+
// the default OpenAPI value — needed so Mustache can emit `query->get(..., <default>)` for php-symfony.
642+
if (p.getDefault() != null) {
643+
Object def = p.getDefault();
644+
if (def instanceof String) {
645+
return "'" + escapeTextInSingleQuotes((String) def) + "'";
646+
}
647+
return def.toString();
648+
}
649+
639650
return null;
640651
}
641652

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

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,12 +216,14 @@ 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).
219221
Assert.assertTrue(
220-
apiContent.contains("?PetModelPetStatus $status"),
222+
Pattern.compile("public function listPets\\(\\s*\\??PetModelPetStatus\\s+\\$status,").matcher(apiContent).find(),
221223
"Expected enum ref query param to use short class in type hint");
222224
Assert.assertTrue(
223-
Pattern.compile("@param\\s+PetModelPetStatus\\|null\\s+\\$status\\b").matcher(apiContent).find(),
224-
"PHPDoc @param should use short PetModelPetStatus|null (consistent with use import)");
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)");
225227
Assert.assertFalse(
226228
apiContent.contains("?\\Org\\OpenAPITools\\Petstore\\Model\\PetModelPetStatus $status"),
227229
"Signature must not use leading-backslash FQCN when a matching use import exists");
@@ -234,6 +236,73 @@ public void testPetstoreDottedEnumRefQueryParameterUsesShortClassInApiInterface(
234236
output.deleteOnExit();
235237
}
236238

239+
/**
240+
* Optional {@code in: query} parameter: {@code required: false}, schema is an enum {@code $ref} with a valid
241+
* {@code default} (see OpenAPI 3.x). Omitting the query key must be equivalent to sending that default; the
242+
* generated controller must not reject the request in validation solely because the value was absent.
243+
* <p>
244+
* Spec: {@code src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml}. Product doc:
245+
* {@code php-symfony.md} section &quot;可选 query:带默认值的非必填枚举 {@code $ref} 缺省却仍被拒绝&quot;.
246+
* <p>
247+
* Expected generated behavior (any one is acceptable):
248+
* <ul>
249+
* <li>Pass the OpenAPI default into {@code Request::query->get} for {@code tone}, and/or</li>
250+
* <li>Apply the Elvis default line ({@code $tone = $tone?:...}) after the read (see {@code api_controller.mustache}), and/or</li>
251+
* <li>Wrap enum {@code Assert\\Type} in {@code Assert\\Optional} for non-required enum refs (see {@code api_input_validation.mustache}).</li>
252+
* </ul>
253+
* Also asserts the integer optional {@code limit} parameter still receives {@code get('limit', 10)} as a control.
254+
* <p>
255+
* <b>Note:</b> This test fails on the generator until optional enum-ref query parameters expose
256+
* {@link org.openapitools.codegen.CodegenParameter#defaultValue} (or equivalent) so templates apply the OpenAPI
257+
* default and/or skip strict {@code Assert\\Type} on {@code null}. It is intended to lock the fix described in the
258+
* php-symfony troubleshooting doc.
259+
*/
260+
@Test
261+
public void testOptionalEnumRefQueryParameterWithDefaultAppliesOpenApiSemantics() throws Exception {
262+
Map<String, Object> properties = new HashMap<>();
263+
properties.put("invokerPackage", "Org\\OpenAPITools\\FeedHints");
264+
properties.put(AbstractPhpCodegen.SRC_BASE_PATH, "src");
265+
266+
File output = Files.createTempDirectory("test").toFile();
267+
268+
final CodegenConfigurator configurator = new CodegenConfigurator()
269+
.setGeneratorName("php-symfony")
270+
.setAdditionalProperties(properties)
271+
.setInputSpec("src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml")
272+
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
273+
274+
final ClientOptInput clientOptInput = configurator.toClientOptInput();
275+
DefaultGenerator generator = new DefaultGenerator();
276+
List<File> files = generator.opts(clientOptInput).generate();
277+
278+
File controllerFile = files.stream()
279+
.filter(f -> "DefaultController.php".equals(f.getName()) && f.getPath().contains("Controller" + File.separator))
280+
.findFirst()
281+
.orElseThrow(() -> new AssertionError("DefaultController.php not generated"));
282+
283+
String controller = Files.readString(controllerFile.toPath(), StandardCharsets.UTF_8);
284+
285+
Assert.assertTrue(
286+
controller.contains("$request->query->get('limit', 10)"),
287+
"Integer optional query with default should pass default as second argument to query->get (control case)");
288+
289+
boolean defaultInGet = Pattern.compile("\\$request->query->get\\('tone',\\s*").matcher(controller).find();
290+
boolean elvisDefault = Pattern.compile("\\$tone\\s*=\\s*\\$tone\\?:").matcher(controller).find();
291+
boolean optionalEnumTypeAssert =
292+
controller.contains("new Assert\\Optional(")
293+
&& controller.contains("PetAnnouncementTone");
294+
295+
Assert.assertTrue(
296+
defaultInGet || elvisDefault || optionalEnumTypeAssert,
297+
"Omitted optional enum-ref query with OpenAPI default must apply default (get/Elvis) and/or use "
298+
+ "Assert\\Optional around enum Type so null is valid before default is applied; "
299+
+ "see optional-enum-query-ref-default.yaml and php-symfony troubleshooting doc");
300+
301+
assertGeneratedPhpSyntaxValid(controllerFile);
302+
303+
output.deleteOnExit();
304+
}
305+
237306
/**
238307
* Runs {@code php -l} on the file. Skips if {@code php} is not available (optional toolchain).
239308
*/
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.
4+
openapi: 3.1.0
5+
info:
6+
title: Optional enum query ref with default (php-symfony repro)
7+
version: '1.0'
8+
paths:
9+
/pets/feed-hints:
10+
get:
11+
operationId: listFeedHints
12+
parameters:
13+
- $ref: '#/components/parameters/ToneQuery'
14+
- name: limit
15+
in: query
16+
required: false
17+
description: Integer optional query with default (often generated correctly).
18+
schema:
19+
type: integer
20+
format: int32
21+
default: 10
22+
minimum: 1
23+
maximum: 50
24+
responses:
25+
'200':
26+
description: OK
27+
components:
28+
parameters:
29+
ToneQuery:
30+
name: tone
31+
in: query
32+
required: false
33+
description: Optional filter; default applies when the query key is omitted.
34+
schema:
35+
$ref: '#/components/schemas/PetAnnouncementTone'
36+
default: friendly
37+
schemas:
38+
PetAnnouncementTone:
39+
type: string
40+
enum:
41+
- friendly
42+
- formal

0 commit comments

Comments
 (0)