fix(graphql): dispatch item Query through its own provider#8237
Merged
soyuka merged 2 commits intoJun 5, 2026
Conversation
1b4a5c6 to
6a6aa85
Compare
When a GraphQL item Query carries `provider:`, dispatch through it instead of routing through the matched HTTP op. Adds identifier-self link to `Query.links` for Doctrine LinksHandler. Fixes api-platform#5805
6a6aa85 to
291b0dc
Compare
Availability::class.'getCase' concatenated to 'AvailabilitygetCase' — a latent typo previously masked because GraphQl item lookups dispatched through the route-matched HTTP Get op. Once Query(provider:) is honoured on its own dispatch path, the broken value surfaces. Add the missing :: so the static-method callable resolves to Availability::getCase.
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
Fixes #5805 — GraphQl item
Query(provider: X)was silently ignored:GraphQl\State\Provider\ReadProviderrouted item lookups throughIriConverter::getResourceFromIri, which dispatched to the route-matched HTTP item op (NotExposed/Get) — bypassing the user-declared Query provider.The first iteration of this PR copied
Query.provideronto the synthesisedNotExposed(band-aid scoped to the no-HTTP-Get case). After exploring three alternatives, this PR moves the fix to the dispatch layer so it covers all combinations of HTTP/GraphQl operations.Implementation (Path A — metadata-time)
1. ReadProvider passes the GraphQl Query to IriConverter when it carries a provider
src/GraphQl/State/Provider/ReadProvider.php— for item Query withprovider:, pass the operation as the 3rd argument toiriConverter->getResourceFromIri. Mutation/Subscription keep the route-matched HTTP op so the class-mismatch diagnostic (Item ... did not match expected type ...) stays accurate.2. IriConverter dispatches via the caller's GraphQl op when it has a provider
src/Symfony/Routing/IriConverter.php+src/Laravel/Routing/IriConverter.php—uri_variablesstill extracted via the HTTProuteOperation. The final dispatch chooses$operation(GraphQl with provider) over$routeOperation(HTTP).3. GraphQl Query.links gets the identifier-self link
src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php— non-collection GraphQl ops getLinkFactory::createLinksFromIdentifiers($op)merged intolinks. Mirrors whatUriTemplateResourceMetadataCollectionFactoryalready does for HTTPuri_variables.4. Doctrine LinksHandler filters root-mode walks
src/Doctrine/Common/State/LinksHandlerTrait::getLinks— when no$context['linkClass']and the op is aGraphQlOperation, keep only links withtoClass === null || toClass === $resourceClass. Drops relation links (which target other classes) so handleLinks appliesWHERE id = Xcleanly. HTTP path unchanged —uri_variablescarry no relation-to-other-class links. Doctrine ODM picks the fix up via the shared trait.src/Laravel/Eloquent/State/LinksHandler— standalone impl, mirror branch added for GraphQl root items.5. Reverted
NotExposedOperationResourceMetadataCollectionFactory— band-aid no longer needed.Why this approach
Considered:
NotExposed.providerinheritance. Works foroperations: []only. Misses#[Get, Query(provider: X)].uri_variablescopied intolinks). Local, lower BC blast radius — but implicit transient mutation.uri_variables. Consumer-side filter (4) keeps existing nested-traversal and HTTP behaviour intact.Path A picked for architectural symmetry —
Query.linksbecomes the single source of truth, mirroring HTTPuri_variables. The LinksHandler filter is a localised change with HTTP unaffected (verified via Subresource tests).BC
Targets
4.3. Three risk vectors audited:IdentifiersExtractor::getIdentifiersFromOperationGraphQl branch reads$op->getLinks()— gets an extra identifier entry. Tests pass.ReadLinkParameterProvider::getUriVariablessame — tests pass.LinksHandlerInterfaceimpls walkingQuery.getLinks()directly would see one extra entry. Documented as a fix.Test plan
tests/Functional/GraphQl/GraphQlCustomQueryProviderTest.php— 3 cases: no-HTTP-Get +Query(provider:X),#[Get(provider:Y), Query(provider:X)](Query wins on GraphQl), HTTP Get keeps own provider.vendor/bin/phpunit tests/Functional/GraphQl— 150 pass, 0 failures.vendor/bin/phpunit tests/Symfony/Routing src/Laravel/Tests/Unit/Routing— 31 pass (IriConverter).vendor/bin/phpunit tests/Functional/SubResource tests/Functional/CustomIdentifierWithSubresourceTest.php— 26 pass (HTTP sub-resource unchanged).vendor/bin/phpunit src/GraphQl/Tests src/Metadata/Tests— only preexisting baseline failures unrelated to this PR.tests/Fixtures/.../Issue5805/→GraphQlCustomQueryProvider/per CLAUDE.md naming rule.