@@ -216,12 +216,20 @@ public void testPetstoreDottedEnumRefQueryParameterUsesShortClassInApiInterface(
216216 Assert .assertTrue (
217217 apiContent .contains ("use Org\\ OpenAPITools\\ Petstore\\ Model\\ PetModelPetStatus;" ),
218218 "Expected enum model import" );
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.
219221 Assert .assertTrue (
220- apiContent .contains ("?PetModelPetStatus $status" ),
221- "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)" );
222227 Assert .assertTrue (
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 (
223231 Pattern .compile ("@param\\ s+PetModelPetStatus\\ |null\\ s+\\ $status\\ b" ).matcher (apiContent ).find (),
224- "PHPDoc @param should use short PetModelPetStatus |null (consistent with use import) " );
232+ "PHPDoc must not document |null for enum ref when OpenAPI default is set " );
225233 Assert .assertFalse (
226234 apiContent .contains ("?\\ Org\\ OpenAPITools\\ Petstore\\ Model\\ PetModelPetStatus $status" ),
227235 "Signature must not use leading-backslash FQCN when a matching use import exists" );
@@ -234,6 +242,92 @@ public void testPetstoreDottedEnumRefQueryParameterUsesShortClassInApiInterface(
234242 output .deleteOnExit ();
235243 }
236244
245+ /**
246+ * Optional {@code in: query} parameter: {@code required: false}, schema is an enum {@code $ref} with a valid
247+ * {@code default} (see OpenAPI 3.x). Omitting the query key must be equivalent to sending that default; the
248+ * generated controller must not reject the request in validation solely because the value was absent.
249+ * <p>
250+ * Spec: {@code src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml}. Product doc:
251+ * {@code php-symfony.md} section "可选 query:带默认值的非必填枚举 {@code $ref} 缺省却仍被拒绝".
252+ * <p>
253+ * Expected generated behavior (any one is acceptable):
254+ * <ul>
255+ * <li>Pass the OpenAPI default into {@code Request::query->get} for {@code tone}, and/or</li>
256+ * <li>Apply the Elvis default line ({@code $tone = $tone?:...}) after the read (see {@code api_controller.mustache}), and/or</li>
257+ * <li>Wrap enum {@code Assert\\Type} in {@code Assert\\Optional} for non-required enum refs (see {@code api_input_validation.mustache}).</li>
258+ * </ul>
259+ * Also asserts the integer optional {@code limit} parameter still receives {@code get('limit', 10)} as a control.
260+ * <p>
261+ * <b>Note:</b> This test fails on the generator until optional enum-ref query parameters expose
262+ * {@link org.openapitools.codegen.CodegenParameter#defaultValue} (or equivalent) so templates apply the OpenAPI
263+ * default and/or skip strict {@code Assert\\Type} on {@code null}. It is intended to lock the fix described in the
264+ * php-symfony troubleshooting doc.
265+ */
266+ @ Test
267+ public void testOptionalEnumRefQueryParameterWithDefaultAppliesOpenApiSemantics () throws Exception {
268+ Map <String , Object > properties = new HashMap <>();
269+ properties .put ("invokerPackage" , "Org\\ OpenAPITools\\ FeedHints" );
270+ properties .put (AbstractPhpCodegen .SRC_BASE_PATH , "src" );
271+
272+ File output = Files .createTempDirectory ("test" ).toFile ();
273+
274+ final CodegenConfigurator configurator = new CodegenConfigurator ()
275+ .setGeneratorName ("php-symfony" )
276+ .setAdditionalProperties (properties )
277+ .setInputSpec ("src/test/resources/3_1/php-symfony/optional-enum-query-ref-default.yaml" )
278+ .setOutputDir (output .getAbsolutePath ().replace ("\\ " , "/" ));
279+
280+ final ClientOptInput clientOptInput = configurator .toClientOptInput ();
281+ DefaultGenerator generator = new DefaultGenerator ();
282+ List <File > files = generator .opts (clientOptInput ).generate ();
283+
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+
303+ File controllerFile = files .stream ()
304+ .filter (f -> "DefaultController.php" .equals (f .getName ()) && f .getPath ().contains ("Controller" + File .separator ))
305+ .findFirst ()
306+ .orElseThrow (() -> new AssertionError ("DefaultController.php not generated" ));
307+
308+ String controller = Files .readString (controllerFile .toPath (), StandardCharsets .UTF_8 );
309+
310+ Assert .assertTrue (
311+ controller .contains ("$request->query->get('limit', 10)" ),
312+ "Integer optional query with default should pass default as second argument to query->get (control case)" );
313+
314+ boolean defaultInGet = Pattern .compile ("\\ $request->query->get\\ ('tone',\\ s*" ).matcher (controller ).find ();
315+ boolean elvisDefault = Pattern .compile ("\\ $tone\\ s*=\\ s*\\ $tone\\ ?:" ).matcher (controller ).find ();
316+ boolean optionalEnumTypeAssert =
317+ controller .contains ("new Assert\\ Optional(" )
318+ && controller .contains ("PetAnnouncementTone" );
319+
320+ Assert .assertTrue (
321+ defaultInGet || elvisDefault || optionalEnumTypeAssert ,
322+ "Omitted optional enum-ref query with OpenAPI default must apply default (get/Elvis) and/or use "
323+ + "Assert\\ Optional around enum Type so null is valid before default is applied; "
324+ + "see optional-enum-query-ref-default.yaml and php-symfony troubleshooting doc" );
325+
326+ assertGeneratedPhpSyntaxValid (controllerFile );
327+
328+ output .deleteOnExit ();
329+ }
330+
237331 /**
238332 * Runs {@code php -l} on the file. Skips if {@code php} is not available (optional toolchain).
239333 */
0 commit comments