Breaking Changes
-
Duplicate Route Protection – Routes with identical patterns now throw
RouterConfigurationExceptionby default. This includes both static routes (e.g.,/api/users) and wildcard routes (e.g.,/api/users/*). Use the$overwriteparameter inentryPoint()to explicitly allow route replacement:$router->entryPoint($pattern, $class, overwrite: true). -
Named Parameter Normalization – Named parameters in route patterns (e.g.,
{id},{userId}) are now normalized to wildcards (*) during registration. This means/user/{id}and/user/{userId}are treated as duplicate patterns and will throw an exception. The parameter names are purely cosmetic and ignored by the router. -
Wildcard Route Tracking – Wildcard routes are now tracked separately and checked for duplicates. Previously, wildcard routes could be registered multiple times without error.
New Features
-
Route Parameter Validation – Added automatic validation to ensure route patterns have sufficient wildcards for all route parameters. When registering routes, the router now validates that each non-DI parameter has a corresponding wildcard in the pattern. Throws
RouterConfigurationExceptionwith a helpful error message if wildcards are missing. Example:#[Route('/user')]withfunction user(int $id)will now throw an exception suggesting to use/user/*. -
Named Parameter Syntax – Added support for
{name}syntax in route patterns as a cosmetic alternative to*. Example:#[Route('/user/{id}/posts/{postId}')]is equivalent to#[Route('/user/*/posts/*')]. Parameter names are for documentation only and do not affect routing behavior. -
Enhanced Middleware Propagation – Fixed middleware propagation from parent entry points to sub-routes discovered via attributes. Routes now properly inherit middleware from their parent entry points when patterns match.
-
RouteUtility::normalizePattern() – Added utility method to centralize pattern normalization logic. Converts
{anything}syntax to*for consistent internal route matching.
Improvements
- Type Safety – Improved type safety in pattern normalization with explicit
LogicExceptionthrows on regex failures. - Better Documentation – Updated README.md and AGENTS.md with comprehensive examples of named parameters, duplicate detection, route coexistence rules, and parameter validation.
- Test Coverage – Added 12 new tests in
RoutePatternTest.phpcovering named parameters, duplicate detection, and route coexistence scenarios. Added 10 new tests inRouteParameterValidationTest.phpcovering parameter validation with wildcards, DI parameters, optional parameters, and variadic parameters.
Migration Guide
If you're upgrading from 1.0.x:
-
Check for duplicate routes: Review your route registrations. If you intentionally register the same pattern twice, add
overwrite: true:// Before (would silently overwrite): $router->entryPoint('/api/users', UserController::class); $router->entryPoint('/api/users', AdminController::class); // After (explicit): $router->entryPoint('/api/users', UserController::class); $router->entryPoint('/api/users', AdminController::class, overwrite: true);
-
Named parameters are normalized: If you relied on different parameter names to create different routes, this will now fail:
// Before (might have worked): #[Route('/user/{id}')] #[Route('/user/{userId}')] // Different name // After (throws exception - they're duplicates): #[Route('/user/*')] // Use different patterns instead #[Route('/user/profile/*')]
-
Wildcard routes checked: Duplicate wildcard patterns now throw exceptions:
// Before (would silently accept): $router->entryPoint('/api/*', Controller1::class); $router->entryPoint('/api/*', Controller2::class); // After (throws exception or use overwrite): $router->entryPoint('/api/*', Controller1::class); $router->entryPoint('/api/*', Controller2::class, overwrite: true);
-
Route parameters must have wildcards: Routes with parameters now require explicit wildcards:
// Before (might have worked): #[Route('/user')] public function user(int $id) { } // After (throws exception - add wildcard): #[Route('/user/*')] public function user(int $id) { } // Or use named parameter syntax: #[Route('/user/{id}')] public function user(int $id) { }
Note: DI parameters (classes, interfaces) don't require wildcards, only route parameters (scalar types, DateTime, enums).