Skip to content

feat(routing): memoize RouteMatcher and share one instance with Router#62

Merged
markshust merged 1 commit into
developfrom
feature/routing-matcher-memoization
May 12, 2026
Merged

feat(routing): memoize RouteMatcher and share one instance with Router#62
markshust merged 1 commit into
developfrom
feature/routing-matcher-memoization

Conversation

@markshust
Copy link
Copy Markdown
Collaborator

Summary

Closes #61.

Eliminates the duplicate route match per request that any global middleware inspecting the upcoming route would trigger (the original surfacing example was PageCacheMiddleware reading #[Cacheable]).

  • Memoize RouteMatcher::match() by (method, path) — both hit and miss results are cached, so repeated calls within a request are O(1). Class is no longer readonly since the cache mutates; the $routes field stays readonly.
  • Share one matcher between container and RouterRouter now takes RouteMatcherInterface via constructor instead of constructing a private RouteMatcher. RoutingBootstrapper creates a single matcher, binds it to the container, and passes the same instance to Router. The RouteCollection $routes parameter is removed from Router (it only existed to construct the now-removed private matcher).

Net effect: a cacheable request still resolves the route exactly once. The middleware's match() populates the cache; the router's later match() is the cache hit.

Test plan

  • Added 3 RouteMatcher memoization tests: per-(method, path) identity, null caching, and key disambiguation by method and by path.
  • Added a RoutingBootstrapper test that reflects into the registered Router and asserts its private $matcher is the same instance the container hands out for RouteMatcherInterface.
  • Updated RouterTest (15 sites) and DemoRoutingTest (2 sites) constructors from routes: $routes to matcher: new RouteMatcher($routes).
  • composer test — 5042 passed / 0 failed
  • phpcs + php-cs-fixer clean on touched files

🤖 Generated with Claude Code

Closes #61.

Global middleware that needs to inspect the upcoming route (e.g.
PageCacheMiddleware reading `#[Cacheable]`) previously paid for a full
route iteration twice per request: once when the middleware called
`RouteMatcherInterface::match()`, and again when `Router::handle()`
called its own private matcher.

Two changes eliminate the duplicate work:

1. **Memoize `RouteMatcher::match()` by (method, path).**
   The matcher caches both hit and miss results so repeated calls for
   the same request are O(1). Class is no longer `readonly` since the
   cache mutates; the `$routes` field remains `readonly`.

2. **Share one RouteMatcher between the container and the Router.**
   `Router` now takes `RouteMatcherInterface` via constructor instead
   of constructing a private `RouteMatcher`. `RoutingBootstrapper`
   creates a single matcher, binds it as `RouteMatcherInterface` in
   the container, and passes the same instance to `Router`. The
   `RouteCollection $routes` parameter is removed from `Router` — it
   only existed to construct the now-removed private matcher.

Net effect: a cacheable request still resolves the route exactly once;
the middleware's lookup populates the cache and the router's lookup
is the cache hit.

Tests:
- Added 3 RouteMatcher memoization tests (per-key identity, null
  caching, key disambiguation by method and path).
- Added a RoutingBootstrapper test that reflects into the registered
  Router and asserts its private `$matcher` is the same instance the
  container hands out for `RouteMatcherInterface`.
- Updated RouterTest and DemoRoutingTest constructors from
  `routes: $routes` to `matcher: new RouteMatcher($routes)`.

`composer test` passes (5042 / 0 failed); `phpcs` + `php-cs-fixer`
clean on touched files.
@github-actions github-actions Bot added the enhancement New feature or request label May 12, 2026
@markshust markshust merged commit 9b6bf94 into develop May 12, 2026
1 check passed
@markshust markshust deleted the feature/routing-matcher-memoization branch May 12, 2026 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Memoize route matching to eliminate duplicate match() calls per request

1 participant