fix: ArrayDescriber splitting arrayKey() union into anyOf [array, object]#2704
fix: ArrayDescriber splitting arrayKey() union into anyOf [array, object]#2704gnutix wants to merge 2 commits into
Conversation
1a128c9 to
3fb5d91
Compare
3fb5d91 to
c471c6b
Compare
There was a problem hiding this comment.
Pull request overview
This PR fixes how ArrayDescriber handles Symfony TypeInfo collection key unions so that array<T> (with the implicit arrayKey(): int|string key) is described as a JSON array rather than being split into an OpenAPI anyOf/oneOf of [array, object]. It also improves handling of Traversable objects that TypeInfo auto-wraps as collections, ensuring object references aren’t lost.
Changes:
- Treat
arrayKey()(int|string) key unions as a list/JSON array instead of splitting into separate keyed array types. - Unwrap
CollectionType(ObjectType)cases (auto-wrapped Traversable objects) and delegate directly so the$refcan be produced. - Add PHPUnit coverage for both behaviors.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/TypeDescriber/ArrayDescriber.php |
Adds special-casing for arrayKey() unions and unwraps Traversable object collections to preserve $ref types. |
tests/TypeDescriber/ArrayDescriberTest.php |
Adds tests to validate list handling for arrayKey() unions and unwrapping behavior for Traversable objects. |
You can also share your feedback on Copilot code review. Take the survey.
c471c6b to
23dc4ee
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 5.x #2704 +/- ##
==========================================
- Coverage 95.59% 95.41% -0.18%
==========================================
Files 94 94
Lines 3064 3078 +14
==========================================
+ Hits 2929 2937 +8
- Misses 135 141 +6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
645c11d to
28ef80c
Compare
When TypeInfo resolves `array<T>` (single generic parameter, no explicit
key type), the key defaults to `arrayKey()` which is `union(int, string)`.
ArrayDescriber was splitting this into separate `array<int, T>` and
`array<string, T>` types, producing `anyOf: [{type: array}, {type: object}]`
in the OpenAPI schema. This is incorrect — `array<T>` in PHP means a
JSON array, not "either array or object".
Additionally, when a Traversable object is used as a generic parameter
(e.g. `list<MyIterableClass>`), StringTypeResolver wraps it in
CollectionType(ObjectType) with no key/value type info. ArrayDescriber
then describes this as `array<unknown>`. The fix detects when the wrapped
type is an ObjectType and delegates to the chain so ClassDescriber can
create a proper `$ref`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
28ef80c to
f8a616a
Compare
|
This PR has been marked as stale because it has not had any activity for 60 days. Remove stale label or comment to prevent this from being closed in 60 days. |
|
We need a code review around here. 🙏 |
Problem
When Symfony's TypeInfo resolves
array<T>(single generic parameter, no explicit key type), the key defaults toarrayKey()— aunion(int, string).ArrayDescribersplits this into separatearray<int, T>andarray<string, T>types, producing an incorrect OpenAPI schema:{ "anyOf": [ { "type": "array", "items": { "$ref": "#/components/schemas/MyDto" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/MyDto" } } ] }The correct output should be just
{ "type": "array", "items": { "$ref": "#/components/schemas/MyDto" } }, sincearray<T>in PHP — especially in the context of JSON API responses — means a sequential array (JSON array), not "either array or object".Traversable objects as generic parameters
When a
Traversableobject is used as a generic parameter (e.g.list<MyIterableClass>),StringTypeResolverwraps it inCollectionType(ObjectType)with no key/value type info.ArrayDescriberthen describes this asarray<unknown>(becausegetCollectionValueType()returnsmixed()), losing the type reference entirely.The fix detects when the wrapped type is an
ObjectType(meaningStringTypeResolverauto-wrapped a Traversable class) and delegates to the chain soClassDescribercan create a proper$ref.Reproduction
With
type_info: true(Symfony ≥ 7.3), therefereesproperty schema includes botharrayandobjecttypes instead of justarray.Relation to #2508
Issue #2508 reported two symptoms:
type_info: false:"items": {}(empty, no type info) — this was caused byConstructorExtractornot reading@varon promoted constructor properties, and was fixed upstream in Symfony 7.4 ([Serialization] with_constructor_extractor: true - @var docs on promoted properties ignored symfony/symfony#60795).type_info: true:"oneOf": [{ "type": "array" }, { "type": "object" }]— this is theArrayDescriberissue fixed by this PR. Even with Symfony 7.4, once TypeInfo correctly reads the type,ArrayDescriberstill splits thearrayKey()union intoanyOf: [array, object].This PR fixes the remaining symptom 2 from #2508.