@@ -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 "可选 query:带默认值的非必填枚举 {@code $ref} 缺省却仍被拒绝".
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 */
0 commit comments