Skip to content

[php-nextgen] Fix Nested oneOf/anyOf Behavior#24318

Open
ckoegel wants to merge 31 commits into
OpenAPITools:masterfrom
ckoegel:php-nextgen-nested-composed
Open

[php-nextgen] Fix Nested oneOf/anyOf Behavior#24318
ckoegel wants to merge 31 commits into
OpenAPITools:masterfrom
ckoegel:php-nextgen-nested-composed

Conversation

@ckoegel

@ckoegel ckoegel commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

This PR builds off of work done in #24306. It flattens composed union type-hints transitively so a nested composed property resolves to its leaf members. Previously, composed property type-hints only listed their direct members, so when a composed type nested another composed type, the deserializer returned a leaf instance the property's union rejected and threw a TypeError.

Once #24306 is merged, I'll update this and mark as ready for review.

PR checklist

  • Read the contribution guidelines.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

@jebentier (2017/07), @dkarlovi (2017/07), @mandrean (2017/08), @jfastnacht (2017/09), @ybelenko (2018/07), @renepardon (2018/12)


Summary by cubic

Adds proper anyOf support and fixes nested oneOf/anyOf handling in php-nextgen by flattening composed unions to leaf types. Adds a cycle-safe flattening pass for models and operations, plus samples (Creature/Zoo) and tests.

  • Bug Fixes

    • Split composed handling into OneOfInterface and AnyOfInterface; ObjectSerializer dispatches via deserializeOneOf/deserializeAnyOf after json_decode, preserves raw strings on decode failure, and improves discriminator/array resolution.
    • Flatten nested oneOf/anyOf union type-hints transitively (applied in model and operation post-processing) so properties accept leaf instances; generate wrappers and update templates, docs, samples, and tests (e.g., CreatureWhale|Zebra|Lizard|Snake).
  • Migration

    • No change to OneOfInterface; AnyOfInterface is new. Regenerate clients.

Written for commit c0c811e. Summary will update on new commits.

Review in cubic

@ckoegel
ckoegel marked this pull request as ready for review July 23, 2026 14:51

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 10 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpNextgenClientCodegen.java">

<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpNextgenClientCodegen.java:213">
P2: Cyclic composed schemas are not fully flattened: revisiting a composed type adds that dispatcher to the generated union as though it were a leaf. Skipping an already-visiting node while adding only non-composed members would preserve cycle safety and the leaf-only contract.</violation>
</file>

<file name="samples/client/petstore/php-nextgen/OpenAPIClient-php/docs/Model/Creature.md">

<violation number="1" location="samples/client/petstore/php-nextgen/OpenAPIClient-php/docs/Model/Creature.md:8">
P2: The documentation directs users to `Mammal` and `Reptile` as concrete values, but those classes are composed-schema wrappers; nested `Creature` values are flattened to `Whale`, `Zebra`, `Lizard`, or `Snake` in the generated API. Listing the leaf models here would make the documented usage match the generated type signatures.</violation>
</file>

<file name="samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Model/Creature.php">

<violation number="1" location="samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Model/Creature.php:59">
P2: `Creature::getAnyOfTypes()` remains nested, so consumers of the generated anyOf metadata see `Mammal|Reptile` instead of the promised leaf union `Whale|Zebra|Lizard|Snake`. Returning the transitive leaf types here keeps direct `Creature` deserialization/introspection consistent with the flattened property types generated elsewhere.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment on lines +213 to +216
if (!composedTypeHints.containsKey(type) || !visiting.add(type)) {
leaves.add(type);
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Cyclic composed schemas are not fully flattened: revisiting a composed type adds that dispatcher to the generated union as though it were a leaf. Skipping an already-visiting node while adding only non-composed members would preserve cycle safety and the leaf-only contract.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpNextgenClientCodegen.java, line 213:

<comment>Cyclic composed schemas are not fully flattened: revisiting a composed type adds that dispatcher to the generated union as though it were a leaf. Skipping an already-visiting node while adding only non-composed members would preserve cycle safety and the leaf-only contract.</comment>

<file context>
@@ -187,6 +188,38 @@ private void collectComposedTypeHint(CodegenModel model, Map<String, String> com
+     * recursively; {@code visiting} guards against cycles in self-referential schemas.
+     */
+    private void collectLeafTypes(String type, Map<String, String> composedTypeHints, Set<String> visiting, Set<String> leaves) {
+        if (!composedTypeHints.containsKey(type) || !visiting.add(type)) {
+            leaves.add(type);
+            return;
</file context>
Suggested change
if (!composedTypeHints.containsKey(type) || !visiting.add(type)) {
leaves.add(type);
return;
}
if (!composedTypeHints.containsKey(type)) {
leaves.add(type);
return;
}
if (!visiting.add(type)) {
return;
}


## anyOf

- [**\OpenAPI\Client\Model\Mammal**](Mammal.md)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The documentation directs users to Mammal and Reptile as concrete values, but those classes are composed-schema wrappers; nested Creature values are flattened to Whale, Zebra, Lizard, or Snake in the generated API. Listing the leaf models here would make the documented usage match the generated type signatures.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/php-nextgen/OpenAPIClient-php/docs/Model/Creature.md, line 8:

<comment>The documentation directs users to `Mammal` and `Reptile` as concrete values, but those classes are composed-schema wrappers; nested `Creature` values are flattened to `Whale`, `Zebra`, `Lizard`, or `Snake` in the generated API. Listing the leaf models here would make the documented usage match the generated type signatures.</comment>

<file context>
@@ -0,0 +1,11 @@
+
+## anyOf
+
+- [**\OpenAPI\Client\Model\Mammal**](Mammal.md)
+- [**\OpenAPI\Client\Model\Reptile**](Reptile.md)
+
</file context>

public static function getAnyOfTypes(): array
{
return [
'\OpenAPI\Client\Model\Mammal',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Creature::getAnyOfTypes() remains nested, so consumers of the generated anyOf metadata see Mammal|Reptile instead of the promised leaf union Whale|Zebra|Lizard|Snake. Returning the transitive leaf types here keeps direct Creature deserialization/introspection consistent with the flattened property types generated elsewhere.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Model/Creature.php, line 59:

<comment>`Creature::getAnyOfTypes()` remains nested, so consumers of the generated anyOf metadata see `Mammal|Reptile` instead of the promised leaf union `Whale|Zebra|Lizard|Snake`. Returning the transitive leaf types here keeps direct `Creature` deserialization/introspection consistent with the flattened property types generated elsewhere.</comment>

<file context>
@@ -0,0 +1,83 @@
+    public static function getAnyOfTypes(): array
+    {
+        return [
+            '\OpenAPI\Client\Model\Mammal',
+            '\OpenAPI\Client\Model\Reptile'
+        ];
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant