Skip to content

fix(metadata): format-agnostic uriTemplate match - #8540

Open
soyuka wants to merge 1 commit into
api-platform:5.0from
soyuka:fix/item-uri-template-format-suffix
Open

soyuka wants to merge 1 commit into
api-platform:5.0from
soyuka:fix/item-uri-template-format-suffix

Conversation

@soyuka

@soyuka soyuka commented Sep 17, 2026

Copy link
Copy Markdown
Member
Q A
Branch? 5.0
Tickets n/a
License MIT
Doc PR n/a

itemUriTemplate and ApiProperty::uriTemplate are resolved to an Operation by comparing the configured string to Operation::getUriTemplate() with a strict ===. Auto-generated operations store the format suffix, so this never matches:

#[ApiResource(operations: [
    new GetCollection(itemUriTemplate: '/books/{id}'),  // stored target is '/books/{id}{._format}'
    new Get(),
])]

OperationMetadataFactory::create() then returns null, and IriConverter::getIriFromResource() silently falls back to the resource's default item operation. The result is a wrong @id, not an error — which is why this is easy to miss. The same strict comparison in ResourceMetadataCollection::getOperation() affects ApiProperty::uriTemplate in the JSON-LD and JSON:API normalizers.

Both call sites now keep the first candidate that matches once the {._format} or .{_format} suffix is stripped from both sides, and return it only after the full scan has found no exact match. Exact matching therefore still wins globally, so two operations differing only by the suffix keep their identities whatever their declaration order. The suffix stripping lives in a new @internal ApiPlatform\Metadata\Util\UriTemplateHelper, which UriTemplateResourceMetadataCollectionFactory now shares instead of carrying its own copy.

Functional coverage is tests/Functional/ItemUriTemplateWithoutFormatSuffixTest.php. Its fixture declares a collection whose itemUriTemplate omits the suffix, plus two item GETs so the fallback is observable. Before the fix:

-'/uri_template_format_suffix_resource_items/1'
+'/uri_template_format_suffix_resources/1'

Unit tests cover the lenient hit, the .{_format} spelling, exact-wins-over-lenient in both declaration orders, and the unchanged null/OperationNotFoundException behaviour for an unrelated template.

@soyuka
soyuka changed the base branch from main to 5.0 September 18, 2026 09:20
`itemUriTemplate` and `ApiProperty::uriTemplate` were compared to
`Operation::getUriTemplate()` with a strict `===`. Auto-generated
operations store the format suffix, so `itemUriTemplate: '/books/{id}'`
never matched `/books/{id}{._format}`. `OperationMetadataFactory::create()`
returned null and the IriConverter silently fell back to the resource's
default item operation, emitting a wrong IRI instead of an error.

`OperationMetadataFactory::create()` and
`ResourceMetadataCollection::getOperation()` now keep the first candidate
that matches once the `{._format}`/`.{_format}` suffix is stripped from
both sides, and return it only after the full scan finds no exact match,
so two operations differing only by the suffix keep their identities
whatever their declaration order.
@soyuka
soyuka force-pushed the fix/item-uri-template-format-suffix branch from 50f74d4 to b0e9e43 Compare September 18, 2026 09:23
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