-
Notifications
You must be signed in to change notification settings - Fork 22
Description
Motivation
The current routing system works well functionally, but several responsibilities are tightly coupled across Router, RouteBuilder, RouteDispatcher, and RouteController.
In particular:
Routeracts as a route registry, builder coordinator, matcher, and runtime authority- Route definition and runtime resolution phases are not clearly separated
- Global helpers depend on
RouteControllerholding runtime route state - Internal method calls rely on
Router::getRoutes()as a global source of truth
This makes the routing subsystem harder to reason about, test, and evolve safely.
The goal of this refactor is not to redesign routing or the DSL, but to introduce clear internal boundaries while keeping current behavior and developer experience intact.
Goals
- Reduce responsibilities of
Routerwithout changing the public DSL - Introduce a dedicated route storage abstraction (
RouteCollection) - Introduce a stateless runtime route resolver (
RouteFinder) - Clarify the lifecycle:
- definition phase → runtime phase
- Preserve all existing route helpers and runtime behavior
Non-Goals
- ❌ No changes to route DSL syntax or semantics
- ❌ No middleware redesign
- ❌ No controller refactor
- ❌ No removal of
RouteControllerstatic runtime state - ❌ No console routing or non-HTTP concerns
- ❌ No PSR-7 or DI refactors
Proposed Architectural Changes
1. RouteCollection (new core class)
Responsibilities:
- Store finalized
Routeobjects - Act as the single source of truth for all registered routes
- Provide query operations (e.g. find by name, group existence)
Constraints:
- Append-only during route definition
- Read-only during runtime
- No matching or dispatch logic
2. RouteFinder (new runtime class)
Responsibilities:
- Match an HTTP request to a route from
RouteCollection - Extract route parameters
- Return a resolved route structure
Constraints:
- Stateless
- Does NOT store or expose global route state
- Does NOT dispatch or execute controllers
3. Router (narrowed responsibilities)
Router should:
- Remain the DSL entry point
- Coordinate route definition using
RouteBuilder - Own a
RouteCollectioninstance - Delegate runtime matching to
RouteFinder
Router should NOT:
- Act as a route registry
- Perform route matching itself
- Serve as a runtime query API
Public API may remain temporarily for compatibility but should delegate internally.
4. RouteBuilder (definition-only)
- Continues to support fluent DSL
- Builds
Routeobjects - Pushes finalized routes into
RouteCollection - Must not leak mutable state beyond definition phase
5. RouteController (runtime context holder)
- Continues to store the current resolved route
- Continues to support existing global helpers
- Receives resolved route data from dispatcher
No behavior change in this ticket.
6. RouteDispatcher (execution boundary)
- Receives a resolved route + request
- Executes middleware and controller
- Does not perform route matching or lookup
Acceptance Criteria
- All existing route DSL files work unchanged
- All existing global route helpers continue to function
- Route matching logic is moved out of
Router Router::getRoutes()usage is internally replaced withRouteCollectionRouteFinderis stateless and testable in isolation- Clear separation between definition and runtime phases exists internally
Tests
- Unit tests for:
RouteCollectionRouteFinder
- Integration tests verifying:
- grouped routes
- middleware inheritance
- named routes
- existing helpers (
current_controller(), etc.)
Notes
This refactor intentionally avoids architectural extremism and focuses on clarity, containment, and future-safety rather than theoretical purity.