Skip to content

fix(php): keep the model namespace for nullable references inside array/map containers - #25667

Open
Inf1mum wants to merge 2 commits into
OpenAPITools:masterfrom
Inf1mum:fix/25574-php-nullable-namespace
Open

Inf1mum wants to merge 2 commits into
OpenAPITools:masterfrom
Inf1mum:fix/25574-php-nullable-namespace

Conversation

@Inf1mum

@Inf1mum Inf1mum commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #25574

What

When a model reference is made nullable and used inside a container — as an array item or as a map value — the PHP generator now emits the fully qualified class name in $openAPITypes:

  • 'deps' => 'array<string,\OpenAPI\Client\Model\Inner>' (previously 'array<string,\OpenAPIClientModelInner>')
  • 'list_deps' => '\OpenAPI\Client\Model\Inner[]' (previously '\OpenAPIClientModelInner[]')

Why

A nullable reference is represented as a composed schema: oneOf/anyOf with the OAS 3.1 null type, or the OAS 3.0 spelling allOf: [$ref] with nullable: true. After the OpenAPI normalizer simplifies such a schema, it resolves in AbstractPhpCodegen.getSchemaType to a fully qualified model name like \OpenAPI\Client\Model\Inner. That name was then passed through toModelName(), which strips the namespace separators (only the leading backslash survives), so the type written into $openAPITypes pointed at a class that does not exist in the global namespace and ObjectSerializer::deserialize failed on every non-null value.

A plain nullable reference as a regular property was unaffected because fromProperty unwraps a single-member allOf before resolving the type. The inner schema of an array or map container reaches getTypeDeclaration as-is and hit the mangling path, which is why both container shapes were broken.

How

AbstractPhpCodegen.getSchemaType now returns an already fully qualified type (leading backslash) as-is instead of feeding it to toModelName().

Both container shapes are covered by tests in PhpClientCodegenTest:

  • a type-helper test asserting dataType for a nullable reference used as a map value and as an array item
  • an end-to-end test asserting the generated $openAPITypes lines for oneOf/anyOf with the 3.1 null type and for the 3.0 allOf-wrapped spelling, with plain (non-nullable) references as controls

Test spec: modules/openapi-generator/src/test/resources/3_1/php/issue_25574.yaml.

Verification: mvn -pl modules/openapi-generator test is green (5757 tests, 0 failures), checkstyle passes, and regenerating samples/client/petstore/php/OpenAPIClient-php from bin/configs/php-OpenAPIClient.yaml produces no content changes.


Summary by cubic

Fixes the PHP generator emitting broken class names for nullable model references inside arrays and maps, so deserialization no longer fails on non-null values.

The generated $openAPITypes now keeps the fully qualified class name, for example 'deps' => 'array<string,\OpenAPI\Client\Model\Inner>' instead of 'array<string,\OpenAPIClientModelInner>'. Previously, a nullable reference resolved to a qualified model name that toModelName() stripped of namespace separators, producing a type pointing at a non-existent class in the global namespace.

Bug Fixes

  • Returns already qualified types (leading \) as-is in getSchemaType instead of passing them through toModelName().
  • Adds type-helper and end-to-end tests covering oneOf/anyOf with the OAS 3.1 null type and the OAS 3.0 allOf-wrapped spelling, with plain references as controls.
  • Regenerates the echo_api php-nextgen and php-nextgen-streaming samples, whose generated output changed to match the generator.

Written for commit 348ca00. Summary will update on new commits.

View guided diff

…ay/map containers

What
- A nullable model reference used as an array item or as a map value now keeps
  its fully qualified class name in the generated $openAPITypes, e.g.
  'array<string,\OpenAPI\Client\Model\Inner>' and
  '\OpenAPI\Client\Model\Inner[]' instead of
  'array<string,\OpenAPIClientModelInner>'.

Why
- Fixes OpenAPITools#25574. A nullable reference (oneOf/anyOf with the OAS 3.1 'null' type,
  or an OAS 3.0 allOf-wrapped $ref with nullable: true) is a composed schema.
  After normalization it resolves to a fully qualified model name in
  AbstractPhpCodegen.getSchemaType, and that name was then passed through
  toModelName(), which strips the namespace separators (only the leading \ is
  kept). The resulting type pointed at a non-existent class in the global
  namespace, so ObjectSerializer::deserialize failed on every non-null value.
  Plain nullable reference properties were unaffected because fromProperty
  unwraps a single-member allOf before resolving the type; the inner schema of
  an array/map container reaches getTypeDeclaration as-is and hit the mangling
  path.

How
- Return an already fully qualified type (leading \) as-is in
  AbstractPhpCodegen.getSchemaType instead of feeding it to toModelName().
- Cover both container shapes in PhpClientCodegenTest: a type-helper test
  asserting dataType for a map value and an array item, and an end-to-end test
  asserting the generated $openAPITypes lines for oneOf/anyOf with the 3.1
  null type and the 3.0 allOf-wrapped spelling, with plain references as
  controls (test spec: 3_1/php/issue_25574.yaml).

@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.

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpClientCodegenTest.java">

<violation number="1" location="modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpClientCodegenTest.java:241">
P2: This test does not exercise the claimed OAS 3.0 case because it loads a 3.1 fixture for both the one/any and all-of assertions. Add a separate 3.0 fixture/test for the `nullable: true` all-of references while retaining this 3.1 fixture for the null-type cases.</violation>
</file>

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

View guided diff | Re-trigger cubic

output.deleteOnExit();

OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_1/php/issue_25574.yaml", null, new ParseOptions()).getOpenAPI();

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: This test does not exercise the claimed OAS 3.0 case because it loads a 3.1 fixture for both the one/any and all-of assertions. Add a separate 3.0 fixture/test for the nullable: true all-of references while retaining this 3.1 fixture for the null-type cases.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpClientCodegenTest.java, line 241:

<comment>This test does not exercise the claimed OAS 3.0 case because it loads a 3.1 fixture for both the one/any and all-of assertions. Add a separate 3.0 fixture/test for the `nullable: true` all-of references while retaining this 3.1 fixture for the null-type cases.</comment>

<file context>
@@ -209,4 +210,65 @@ public void testDateTimeLengthValidationIsNotGenerated() throws Exception {
+        output.deleteOnExit();
+
+        OpenAPI openAPI = new OpenAPIParser()
+                .readLocation("src/test/resources/3_1/php/issue_25574.yaml", null, new ParseOptions()).getOpenAPI();
+
+        codegen.setOutputDir(output.getAbsolutePath());
</file context>

…helper fix

Regenerate the echo_api php-nextgen and php-nextgen-streaming samples via
bin/generate-samples.sh after AbstractPhpCodegen.getSchemaType keeps fully
qualified types as-is: the generated BodyApi/QueryApi sources and docs now
match the generator output on a clean Linux run (verified stable across two
consecutive regeneration passes).

Signed-off-by: Inf1mum <vadimlazuk5@gmail.com>

@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 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="samples/client/echo_api/php-nextgen-streaming/docs/Api/BodyApi.md">

<violation number="1" location="samples/client/echo_api/php-nextgen-streaming/docs/Api/BodyApi.md:22">
P3: The regenerated BodyApi documentation now shows doubled backslashes in PHP namespace names, making the signature and parameter examples misleading. Keep one `\` between namespace components in all rendered PHP examples.</violation>
</file>

<file name="samples/client/echo_api/php-nextgen-streaming/src/Api/BodyApi.php">

<violation number="1" location="samples/client/echo_api/php-nextgen-streaming/src/Api/BodyApi.php:167">
P0: The regenerated API contains doubled namespace separators in PHP type declarations, so `BodyApi.php` cannot be parsed. Emit a single `\` between each namespace component throughout the generated declarations and type strings.</violation>
</file>

<file name="samples/client/echo_api/php-nextgen-streaming/src/Api/QueryApi.php">

<violation number="1" location="samples/client/echo_api/php-nextgen-streaming/src/Api/QueryApi.php:1499">
P2: The generated query helper passes `&amp;` as literal `openApiType` metadata instead of `&`. Emit `\OpenAPI\Client\Model\Bird&\OpenAPI\Client\Model\Category` so the PHP sample does not leak HTML escaping into serializer metadata.</violation>
</file>

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

View guided diff | Re-trigger cubic

public function testBinaryGif(
string $contentType = self::contentTypes['testBinaryGif'][0]
): \Psr\Http\Message\StreamInterface
): \Psr\\Http\\Message\\StreamInterface

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.

P0: The regenerated API contains doubled namespace separators in PHP type declarations, so BodyApi.php cannot be parsed. Emit a single \ between each namespace component throughout the generated declarations and type strings.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At samples/client/echo_api/php-nextgen-streaming/src/Api/BodyApi.php, line 167:

<comment>The regenerated API contains doubled namespace separators in PHP type declarations, so `BodyApi.php` cannot be parsed. Emit a single `\` between each namespace component throughout the generated declarations and type strings.</comment>

<file context>
@@ -160,11 +160,11 @@ public function getConfig(): Configuration
     public function testBinaryGif(
         string $contentType = self::contentTypes['testBinaryGif'][0]
-    ): \Psr\Http\Message\StreamInterface
+    ): \Psr\\Http\\Message\\StreamInterface
     {
         list($response) = $this->testBinaryGifWithHttpInfo($contentType);
</file context>

$query_object,
'query_object', // param base name
'\OpenAPIClientModelBirdOpenAPIClientModelCategory', // openApiType
'\OpenAPI\Client\Model\Bird&amp;\OpenAPI\Client\Model\Category', // openApiType

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 generated query helper passes &amp; as literal openApiType metadata instead of &. Emit \OpenAPI\Client\Model\Bird&\OpenAPI\Client\Model\Category so the PHP sample does not leak HTML escaping into serializer metadata.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At samples/client/echo_api/php-nextgen-streaming/src/Api/QueryApi.php, line 1499:

<comment>The generated query helper passes `&amp;` as literal `openApiType` metadata instead of `&`. Emit `\OpenAPI\Client\Model\Bird&\OpenAPI\Client\Model\Category` so the PHP sample does not leak HTML escaping into serializer metadata.</comment>

<file context>
@@ -1496,7 +1496,7 @@ public function testQueryStyleDeepObjectExplodeTrueObjectAllOfRequest(
             $query_object,
             'query_object', // param base name
-            '\OpenAPIClientModelBirdOpenAPIClientModelCategory', // openApiType
+            '\OpenAPI\Client\Model\Bird&amp;\OpenAPI\Client\Model\Category', // openApiType
             'deepObject', // style
             true, // explode
</file context>
Suggested change
'\OpenAPI\Client\Model\Bird&amp;\OpenAPI\Client\Model\Category', // openApiType
'\OpenAPI\Client\Model\Bird&\OpenAPI\Client\Model\Category', // openApiType


```php
testBinaryGif(): \Psr\Http\Message\StreamInterface
testBinaryGif(): \Psr\\Http\\Message\\StreamInterface

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.

P3: The regenerated BodyApi documentation now shows doubled backslashes in PHP namespace names, making the signature and parameter examples misleading. Keep one \ between namespace components in all rendered PHP examples.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At samples/client/echo_api/php-nextgen-streaming/docs/Api/BodyApi.md, line 22:

<comment>The regenerated BodyApi documentation now shows doubled backslashes in PHP namespace names, making the signature and parameter examples misleading. Keep one `\` between namespace components in all rendered PHP examples.</comment>

<file context>
@@ -19,7 +19,7 @@ All URIs are relative to http://localhost:3000, except if the operation defines
 
 ```php
-testBinaryGif(): \Psr\Http\Message\StreamInterface
+testBinaryGif(): \Psr\\Http\\Message\\StreamInterface

</file context>


</details>

This branch has not been deployed

No deployments
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.

[BUG][PHP] Nullable model reference in an array or map loses its namespace separators

1 participant