fix(metadata): resolve uri variable identifiers from metadata - #8578
Merged
soyuka merged 3 commits intoSep 25, 2026
Merged
Conversation
property_exists() cannot see Eloquent magic attributes (columns are exposed through __get, not declared properties), so the uri variable "guess" fallback silently picked the literal 'id' instead of the real identifier. Consult the link factory's own identifier resolution (completeLink(), which reads isIdentifier() from property metadata) before falling back to the old guess, so Laravel models with a custom $primaryKey (e.g. a UUID) get their real identifier instead of 'id'. Fixes api-platform#8167
LinkFactory::getIdentifiersFromResourceClass() cached an empty identifiers array forever once computed. On a long-lived worker (e.g. Octane), or when routes register before migrations run, a resource's identifiers can resolve empty on the first call and stay poisoned for the life of the process, even once the underlying table becomes available. Mirrors ModelMetadata::getAttributes()'s guard for the same race. Fixes api-platform#8571
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
UriTemplateResourceMetadataCollectionFactory::configureUriVariables()falls back to a"guess" when the number of uri template variables doesn't match the resolved uri
variables. That guess used
property_exists($operation->getClass(), $variable)to decidewhether the missing variable is a real identifier or should default to
'id'.Eloquent models expose their columns as magic attributes through
__get/__set, never asdeclared PHP properties, so
property_exists()is alwaysfalsefor them - even when themodel has a custom
$primaryKey(e.g. a UUID column). The guess silently picked the literalstring
'id'instead of the real identifier, which made route/IRI generation fail.The fix replaces the
property_exists()guess with the link factory's own identifierresolution (
LinkFactoryInterface::completeLink()), already used a few lines above in thesame method. It resolves identifiers via
getIdentifiersFromResourceClass(), which readsdeclared property metadata (
isIdentifier()) instead of raw PHP property existence - onLaravel this correctly resolves a custom
$primaryKeysuch as'uuid'. The previousguess is kept as a fallback for the (rare) case where
completeLink()still can't resolveany identifier, so no existing behavior regresses.
Reproduction
A Laravel Eloquent model using a non-
idstring primary key (e.g.$primaryKey = 'uuid')with an explicit
Get(uriTemplate: '/buildings/{uuid}')operation and no explicituriVariablescould get its identifier silently guessed as'id'when the uri variableshad to be rebuilt from scratch (e.g. stale/legacy-loaded uri variables that no longer match
the current uri template), instead of being resolved from metadata. Route/IRI generation
then throws
Unable to generate an IRI for the item of type "...".Test plan
UriTemplateResourceMetadataCollectionFactoryTest::testResolvesMissingUriVariableIdentifierFromMetadataInsteadOfGuessingId, which fails with the oldproperty_exists()guess (resolves to'id') and passes with the fix (resolves to'uuid').Buildingmodel/migration/factory +UuidPrimaryKeyIriTest) matching the reported example (UUID primary key, explicit CRUD operations, JSON:API collection request) to guard against regressions on that shape.UriTemplateResourceMetadataCollectionFactoryTest::testCreatestill passes.Fixes #8167