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 intonelmio:5.xfrom
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
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.