From b66b1e9d361280f09c96be9c5aacad4bf85435f5 Mon Sep 17 00:00:00 2001 From: Jelle van Oosterbosch Date: Sun, 30 Aug 2026 18:10:39 +0200 Subject: [PATCH 1/2] Skip only the offending area when a locator entry is unusable matchesByRouteName() returned false as soon as a locator entry was not a RouteCollection, abandoning every area after it. A single unusable entry therefore made routes in later areas look like non-API routes, silently turning off request validation and RFC 7807 error responses for them. Continue past the bad entry instead. testNonRouteCollectionServiceCausesFalse asserted the old behaviour with a fixture whose route sat in the very area that went unchecked; it now asserts the route is found, with a companion test keeping false for a locator that offers nothing usable at all. --- src/Routing/NelmioAreaRoutesChecker.php | 4 +++- tests/Unit/Routing/NelmioAreaRoutesTest.php | 22 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Routing/NelmioAreaRoutesChecker.php b/src/Routing/NelmioAreaRoutesChecker.php index fdbed47..3ff3cf9 100644 --- a/src/Routing/NelmioAreaRoutesChecker.php +++ b/src/Routing/NelmioAreaRoutesChecker.php @@ -50,7 +50,9 @@ private function matchesByRouteName(string $routeName): bool foreach (array_keys($this->routesLocator->getProvidedServices()) as $area) { $routeCollection = $this->routesLocator->get($area); if (!$routeCollection instanceof RouteCollection) { - return false; + // Skip only this area. Bailing out here would leave the remaining areas unchecked, so a + // single unusable entry would silently turn API routes into non-API ones. + continue; } if (null !== $routeCollection->get($routeName)) { diff --git a/tests/Unit/Routing/NelmioAreaRoutesTest.php b/tests/Unit/Routing/NelmioAreaRoutesTest.php index 94480af..ae889ff 100644 --- a/tests/Unit/Routing/NelmioAreaRoutesTest.php +++ b/tests/Unit/Routing/NelmioAreaRoutesTest.php @@ -103,8 +103,10 @@ public function testReturnsTrueWhenFoundInSecondArea(): void self::assertTrue($checker->isApiRoute($request)); } - public function testNonRouteCollectionServiceCausesFalse(): void + public function testNonRouteCollectionServiceOnlySkipsItsOwnArea(): void { + // Arrange — the unusable entry comes first, so bailing out on it would leave the area that + // actually holds the route unchecked. $notARouteCollection = static fn () => (object) ['not' => 'a route collection']; $collection = new RouteCollection(); @@ -121,6 +123,24 @@ public function testNonRouteCollectionServiceCausesFalse(): void $request = new Request(); $request->attributes->set('_route', 'would_match'); + // Act & Assert + self::assertTrue($checker->isApiRoute($request)); + } + + public function testUnusableAreasAloneStillReturnFalse(): void + { + // Arrange + /** @var ServiceLocator $locator */ + $locator = new ServiceLocator([ + 'not_a_route_collection' => static fn () => (object) ['not' => 'a route collection'], + ]); + + $checker = new NelmioAreaRoutesChecker($locator); + + $request = new Request(); + $request->attributes->set('_route', 'anything'); + + // Act & Assert self::assertFalse($checker->isApiRoute($request)); } From ef7730f35c4d0c9e2e033f097f0b66a55e302553 Mon Sep 17 00:00:00 2001 From: Jelle van Oosterbosch Date: Sun, 30 Aug 2026 18:15:53 +0200 Subject: [PATCH 2/2] Drop the comment restating what continue already says --- src/Routing/NelmioAreaRoutesChecker.php | 2 -- tests/Unit/Routing/NelmioAreaRoutesTest.php | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Routing/NelmioAreaRoutesChecker.php b/src/Routing/NelmioAreaRoutesChecker.php index 3ff3cf9..cf3cbf5 100644 --- a/src/Routing/NelmioAreaRoutesChecker.php +++ b/src/Routing/NelmioAreaRoutesChecker.php @@ -50,8 +50,6 @@ private function matchesByRouteName(string $routeName): bool foreach (array_keys($this->routesLocator->getProvidedServices()) as $area) { $routeCollection = $this->routesLocator->get($area); if (!$routeCollection instanceof RouteCollection) { - // Skip only this area. Bailing out here would leave the remaining areas unchecked, so a - // single unusable entry would silently turn API routes into non-API ones. continue; } diff --git a/tests/Unit/Routing/NelmioAreaRoutesTest.php b/tests/Unit/Routing/NelmioAreaRoutesTest.php index ae889ff..002601b 100644 --- a/tests/Unit/Routing/NelmioAreaRoutesTest.php +++ b/tests/Unit/Routing/NelmioAreaRoutesTest.php @@ -105,8 +105,7 @@ public function testReturnsTrueWhenFoundInSecondArea(): void public function testNonRouteCollectionServiceOnlySkipsItsOwnArea(): void { - // Arrange — the unusable entry comes first, so bailing out on it would leave the area that - // actually holds the route unchecked. + // Arrange — order matters: the unusable entry must come before the area holding the route. $notARouteCollection = static fn () => (object) ['not' => 'a route collection']; $collection = new RouteCollection();