[php-nextgen] Add anyof Support#24306
Conversation
There was a problem hiding this comment.
All reported issues were addressed across 35 files
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| supportingFiles.add(new SupportingFile("ObjectSerializer.mustache", toSrcPath(invokerPackage, srcBasePath), "ObjectSerializer.php")); | ||
| supportingFiles.add(new SupportingFile("ModelInterface.mustache", toSrcPath(modelPackage, srcBasePath), "ModelInterface.php")); | ||
| supportingFiles.add(new SupportingFile("OneOfInterface.mustache", toSrcPath(modelPackage, srcBasePath), "OneOfInterface.php")); | ||
| supportingFiles.add(new SupportingFile("ComposedInterface.mustache", toSrcPath(modelPackage, srcBasePath), "ComposedInterface.php")); |
There was a problem hiding this comment.
What about keeping the OneOfInterface and adding a AnyOfInterface instead?
Renaming OneOfInterface to ComposedInterface is a breaking change if I'm not mistaken.
For other generators we do maintain separate implementations (mustache files) for anyOf and oneOf.
There was a problem hiding this comment.
I'm definitely not opposed to splitting them out, I implemented it this way since the two are so similar to avoid some more duplication. But if we want to match some of the other generators we can definitely migrate to 2 separate models.
This is technically breaking, but the original oneOf implementation hasn't been released yet, so the only people this would break is those using 7.24.0-SNAPSHOT. If that's an issue then we can definitely break these out into oneOf/anyOf. Let me know and I can start on that
There was a problem hiding this comment.
@coffeemakr can you please also review this change when you've time since this is a follow-up PR to #23985 authored by you?
There was a problem hiding this comment.
Personally I prefer making the implementation more consistent to other generators. In addition, the term Composed may not be familiar to developers who just start using OpenAPI in their workflow.
There was a problem hiding this comment.
Sounds good, I'll get started on this rn
|
also cc @coffeemakr @JulianVennen (top contributors to php-nextgen client generator) |
…into php-nextgen-anyof
There was a problem hiding this comment.
3 issues found across 36 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/resources/php-nextgen/ObjectSerializer.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/php-nextgen/ObjectSerializer.mustache:501">
P1: Nested `anyOf` string values fail to deserialize because arbitrary property strings are JSON-decoded before dispatch. Preserve non-JSON strings (or dispatch before this decode) so an `anyOf` containing `string` can receive its original value.</violation>
</file>
<file name="samples/client/petstore/php-nextgen/OpenAPIClient-php/src/ObjectSerializer.php">
<violation number="1" location="samples/client/petstore/php-nextgen/OpenAPIClient-php/src/ObjectSerializer.php:641">
P1: `anyOf` responses matching multiple members lose data because this returns the first valid member. Preserve all matching member data (or generate a composed representation); `Smoothie` with only `lengthCm` currently becomes an empty `Apple`.</violation>
</file>
<file name="samples/client/echo_api/php-nextgen-streaming/src/ObjectSerializer.php">
<violation number="1" location="samples/client/echo_api/php-nextgen-streaming/src/ObjectSerializer.php:632">
P2: Scalar anyOf members are selected by declaration order rather than JSON type: `settype()` coerces every non-null scalar, so `[integer, string]` deserializes `"abc"` as `0`. Validate a primitive candidate before coercion, then continue to the next member when its input type does not match.</violation>
</file>
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
| return self::deserializeOneOf($data, $class, $httpHeaders); | ||
| } | ||
| if (is_subclass_of($class, '\{{modelPackage}}\AnyOfInterface')) { | ||
| return self::deserializeAnyOf($data, $class, $httpHeaders); |
There was a problem hiding this comment.
P1: Nested anyOf string values fail to deserialize because arbitrary property strings are JSON-decoded before dispatch. Preserve non-JSON strings (or dispatch before this decode) so an anyOf containing string can receive its original value.
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/resources/php-nextgen/ObjectSerializer.mustache, line 501:
<comment>Nested `anyOf` string values fail to deserialize because arbitrary property strings are JSON-decoded before dispatch. Preserve non-JSON strings (or dispatch before this decode) so an `anyOf` containing `string` can receive its original value.</comment>
<file context>
@@ -492,14 +492,18 @@ class ObjectSerializer
return self::deserializeOneOf($data, $class, $httpHeaders);
}
+ if (is_subclass_of($class, '\{{modelPackage}}\AnyOfInterface')) {
+ return self::deserializeAnyOf($data, $class, $httpHeaders);
+ }
+
</file context>
|
|
||
| if ($instance instanceof ModelInterface) { | ||
| if ($instance->valid()) { | ||
| return $instance; |
There was a problem hiding this comment.
P1: anyOf responses matching multiple members lose data because this returns the first valid member. Preserve all matching member data (or generate a composed representation); Smoothie with only lengthCm currently becomes an empty Apple.
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/ObjectSerializer.php, line 641:
<comment>`anyOf` responses matching multiple members lose data because this returns the first valid member. Preserve all matching member data (or generate a composed representation); `Smoothie` with only `lengthCm` currently becomes an empty `Apple`.</comment>
<file context>
@@ -587,6 +598,56 @@ private static function deserializeOneOf(mixed $data, string $class, ?array $htt
+
+ if ($instance instanceof ModelInterface) {
+ if ($instance->valid()) {
+ return $instance;
+ }
+ } elseif ($instance !== null) {
</file context>
|
|
||
| foreach ($class::getAnyOfTypes() as $type) { | ||
| try { | ||
| $instance = self::deserialize($data, $type, $httpHeaders); |
There was a problem hiding this comment.
P2: Scalar anyOf members are selected by declaration order rather than JSON type: settype() coerces every non-null scalar, so [integer, string] deserializes "abc" as 0. Validate a primitive candidate before coercion, then continue to the next member when its input type does not match.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/echo_api/php-nextgen-streaming/src/ObjectSerializer.php, line 632:
<comment>Scalar anyOf members are selected by declaration order rather than JSON type: `settype()` coerces every non-null scalar, so `[integer, string]` deserializes `"abc"` as `0`. Validate a primitive candidate before coercion, then continue to the next member when its input type does not match.</comment>
<file context>
@@ -588,6 +599,56 @@ private static function deserializeOneOf(mixed $data, string $class, ?array $htt
+
+ foreach ($class::getAnyOfTypes() as $type) {
+ try {
+ $instance = self::deserialize($data, $type, $httpHeaders);
+ } catch (\Throwable $e) {
+ // Any failure means the data does not match this member type; try the next one.
</file context>
Follow up to #23985
This PR adds support for anyOf models with and without discriminators. It copies the implementation from the oneOf model and updates some of the naming/logic to refer to
Composedinstead ofoneOforanyOfspecifically.PR checklist
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.
@jebentier (2017/07), @dkarlovi (2017/07), @mandrean (2017/08), @jfastnacht (2017/09), @ybelenko (2018/07), @renepardon (2018/12)
Summary by cubic
Adds anyOf support to the PHP Nextgen generator with a new
AnyOfInterfaceand generated dispatcher models. UpdatesObjectSerializerto resolveoneOf/anyOfright afterjson_decodeand before the array-to-object cast/normalization to keep arrays and scalars intact.New Features
anyOfdispatcher models viamodel_anyof.mustache; addAnyOfInterfaceand register it inPhpNextgenClientCodegen.deserializeAnyOf()and dispatch for bothOneOfInterfaceandAnyOfInterfaceinObjectSerializer.anyOfmembers and discriminator selection; skip per-property test stubs for composed models; addanyOftests; regenerate samples/docs/tests.Bug Fixes
oneOf/anyOfbefore the array-to-object cast and normalization inObjectSerializerto avoid corrupting arrays/scalars.Throwableduring deserialization.Written for commit 457f0f4. Summary will update on new commits.