Skip to content

fix(serializer): accept union-typed IRI collections on denormalization#8339

Merged
soyuka merged 1 commit into
api-platform:4.3from
soyuka:fix/union-iri-collection-denormalization
Jun 22, 2026
Merged

fix(serializer): accept union-typed IRI collections on denormalization#8339
soyuka merged 1 commit into
api-platform:4.3from
soyuka:fix/union-iri-collection-denormalization

Conversation

@soyuka

@soyuka soyuka commented Jun 22, 2026

Copy link
Copy Markdown
Member
Q A
Branch? 4.3
Tickets Closes #8335
License MIT

Problem

The type-confusion guard added in 6bcbeb2 (GHSA-9rjg-x2p2-h68h) validates an IRI's resolved resource against a single declared class via is_a($item, $resourceClass).

For a collection typed array<Foo|Bar>, AbstractItemNormalizer::createAndValidateAttributeValue resolves one class for the whole collection — UnionType::isSatisfiedBy short-circuits at the first member, so $className = Foo. An IRI pointing to the second member (/api/bars/2) then resolves to a Bar, fails is_a(Bar, Foo), and the whole collection is rejected with a misleading 422. Unlike the scalar union case, the collection path has no $isMultipleTypes retry loop, so there is no fallback.

This is the sibling of #8329 (scalar union, fixed in #8333) on the collection path.

Fix

Validate the resolved item against the declared relation Type via Type::isSatisfiedBy instead of a single class string — TypeInfo already models union/intersection membership, so no class list needs to be hand-rolled.

The declared relation native type is threaded through context['relation_native_type'] (collection value type for collections, $t for scalar relations). getResourceFromIri uses it when present and falls back to is_a() otherwise, leaving the single-class and legacy-type paths unchanged.

The security guard still rejects an IRI whose resource resolves outside the declared union.

Tests

  • Unit: AbstractItemNormalizerTest::testUnionTypeCollectionDenormalizationAcceptsAnyMember — reproduces the exact array<Foo|Bar> stack, fails before / passes after. Full Serializer component suite green (104 tests).
  • Functional: tests/Functional/UnionIriCollectionTest — POSTs IRIs of each union member into an array<Foo|Bar> collection, expects 201 with both resolved.

Note: the functional harness wouldn't boot in my local sandbox (kernel warmup hangs, unrelated to this change). The unit test covers the precise denormalization path; CI validates the functional one.

The type-confusion guard added in 6bcbeb2 (GHSA-9rjg-x2p2-h68h) validates
an IRI's resolved resource against a single declared class via is_a(). For a
collection typed array<Foo|Bar>, createAndValidateAttributeValue resolves one
class for the whole collection (isSatisfiedBy short-circuits at the first
union member), so an IRI pointing to the second member (/api/bars/2) is
rejected and the whole collection fails - there is no union retry loop on the
collection path (api-platform#8335).

Validate the resolved item against the declared relation Type via
Type::isSatisfiedBy instead of a single class string. The relation native type
is threaded through context['relation_native_type']; the guard falls back to
is_a() when absent, keeping the single-class and legacy-type paths unchanged.
The security guard still rejects IRIs resolving outside the declared union.

Sibling of api-platform#8329 (scalar union, fixed in api-platform#8333) on the collection path.

Closes api-platform#8335
@soyuka soyuka merged commit 7bc11b2 into api-platform:4.3 Jun 22, 2026
108 of 112 checks passed
@soyuka

soyuka commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

lowest failures are expected.

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