fix(symfony): throw on route name collisions between resource classes - #8502
Open
audain-dg wants to merge 1 commit into
Open
fix(symfony): throw on route name collisions between resource classes#8502audain-dg wants to merge 1 commit into
audain-dg wants to merge 1 commit into
Conversation
audain-dg
force-pushed
the
fix/route-name-collision-across-resources-main
branch
from
September 5, 2026 08:52
c3192bb to
41d495f
Compare
Operation names double as Symfony route names, and `ApiLoader` registered them with `RouteCollection::add()`, which silently replaces an existing entry. Two resource classes exposing the same URI template and method therefore produced one route only, owned by whichever class was discovered last, without any error or warning. `MetadataCollectionFactoryTrait::assertOperationNameIsUnique()` already rejects a duplicate name within one class; this extends the guarantee across classes: - two exposed operations from different classes under one name throw a `RuntimeException` naming both classes and pointing to `routeName` as the way to share a route on purpose; - a `NotExposed` placeholder never wins over an exposed operation of another class, whatever the discovery order, and never throws. The test application had four such collisions, each dropping a route silently: `Issue7916\UserActionResource` vs its ODM twin, `DummyResourceWithComplexConstructor` vs `Employee`, and two `NotExposed` placeholders shadowing `Book` and `Person`. The first two now use distinct URI templates, the last two are handled by the placeholder rule.
audain-dg
force-pushed
the
fix/route-name-collision-across-resources-main
branch
from
September 5, 2026 08:54
41d495f to
eddafae
Compare
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.
What
Operation names double as Symfony route names, and
ApiLoaderregisters them withRouteCollection::add(), which silently replaces an existing entry. Two resource classes exposing the same URI template and method therefore end up with one route only, owned by whichever class was discovered last, with no error or warning. The winner can flip oncache:clearwhen the discovery order changes.MetadataCollectionFactoryTrait::assertOperationNameIsUnique()(4.3) already rejects a duplicate name within one class. This PR extends the guarantee across classes:RuntimeExceptionnaming both classes, and pointing torouteNameas the supported way to share a route on purpose;NotExposedplaceholder never wins over an exposed operation of another class, whatever the discovery order, and never throws (a DTO and an entity sharing ashortNamekeep working).Why it matters
The test application itself had four such collisions, each silently dropping a route:
_api_/user-actions_get_collectionIssue7916\UserActionResource(ORM)UserActionResourceOdm_api_/companies/{companyId}/employees/{id}_getDummyResourceWithComplexConstructorEmployee_api_/books/{id}{._format}_getIriFilterRelationsTest\Book(NotExposed)Entity\Book_api_/people/{id}{._format}_getIssue5438\Person(NotExposed)Entity\PersonNobody noticed because functional tests restrict resources per test class. In a real application the same thing happens when two resources (an ORM/ODM pair, a DTO and its entity, two projections of one entity) share a URI template.
The first two fixtures now use distinct URI templates; the last two are covered by the placeholder rule.
Notes
mainbecause a silent overwrite becomes an exception. Happy to retarget4.3if you consider it a plain bug fix.ApiLoaderTest::testApiLoaderwas assertingRelatedDummyEntity::classon every route, i.e. it was asserting the overwrite: both classes returned the same metadata collection. The helper now gives the second class an empty collection by default.routeNamecreates no route (ApiLoaderskips it) and is not affected. Covered by a new test.