Skip to content

adds url-rewriting#2

Closed
ljonesfl wants to merge 5 commits intodevelopfrom
feature/url-rewriting
Closed

adds url-rewriting#2
ljonesfl wants to merge 5 commits intodevelopfrom
feature/url-rewriting

Conversation

@ljonesfl
Copy link
Member

@ljonesfl ljonesfl commented Jan 12, 2026

Note

Introduces internal URL rewrite support and strict duplicate-route detection with runtime toggles and clearer error reporting.

  • New DuplicateRouteException and router registries to detect duplicate method+path and name conflicts; strict mode enabled by default with setStrictMode(false) to relax
  • Router APIs: setUrlRewrites, transparent rewriteUrl in run, clearRoutes, name registration via registerRouteName/unregisterRouteName; get/post/put/delete accept optional name
  • RouteMap gains router context and validated setName to participate in name-dup checks
  • README: switch to config/routing.yaml precedence, add URL rewrite docs, examples, and duplicate detection guidance; remove hybrid YAML+attributes section
  • Tests: new cases for duplicate paths/names and strict mode behavior; improved isolation by clearing routes between tests

Written by Cursor Bugbot for commit fdd3815. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • URL rewrites for internal, non-redirect remapping; duplicate-route detection with optional strict enforcement and runtime toggle; runtime APIs to set rewrites, clear routes, and assign route names.
  • Documentation

    • Expanded routing docs with rewrite usage, examples, behavior vs redirects, and routing.yaml precedence; removed legacy hybrid routing guidance.
  • Tests

    • New tests for duplicate-route and strict-mode behaviors; improved test isolation by clearing routes between tests.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 12, 2026

📝 Walkthrough

Walkthrough

Adds URL rewrite processing before route matching, strict duplicate-route detection with named-route tracking, router state APIs (setUrlRewrites, setStrictMode, clearRoutes), RouteMap router-context name registration, a new DuplicateRouteException, and test updates to isolate routes between tests.

Changes

Cohort / File(s) Summary
Documentation
README.md
Moved attribute-routing config to config/routing.yaml; added URL Rewrites section with examples, configuration, behavior, and rewrite vs redirect comparison; removed Hybrid Approach section; updated routing examples and config excerpts.
Exceptions
src/Routing/Exceptions/DuplicateRouteException.php
New exception class capturing first/second route method, path, definitions, and optional name; constructs detailed message and exposes getters for all stored details.
Core Routing Logic
src/Routing/Router.php
Added URL rewrites storage and setUrlRewrites(); applies rewrites pre-matching via rewriteUrl(); added strict duplicate detection and registries ($_registeredRoutes, $_registeredNames) with setStrictMode() and clearRoutes(); extended addRoute() and HTTP-verb helpers to accept method and optional name; added checkDuplicateRoute(), registerRouteName(), unregisterRouteName(), extractControllerInfo(), getRouteByName(), generateUrl(), and getAllNamedRoutes().
RouteMap
src/Routing/RouteMap.php
Added router/context fields $_router, $_method and setRouterContext(Router $router, string $method); setName(string $name) now coordinates with Router to unregister/register names when context exists.
Tests
tests/unit/RouteTest.php, tests/unit/RouterTest.php
RouteTest adds tearDown() calling Router::getInstance()->clearRoutes() to isolate tests; RouterTest adds tests for duplicate-route exceptions, duplicate names (fluent and parameter), cross-method name conflicts, and relaxed strict-mode behavior allowing duplicates.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Router
  participant RouteStore
  participant Dispatcher
  Client->>Router: HTTP request (URL)
  Router->>Router: rewriteUrl(original URL)
  Router->>RouteStore: find matching route for rewritten URL
  alt route found
    Router->>Dispatcher: dispatch(controller/action)
    Dispatcher->>Client: response
  else route not found
    Router->>Client: 404 response
  end
Loading
sequenceDiagram
  participant Dev
  participant Router
  participant RouteStore
  participant Exception
  Dev->>Router: addRoute(method, path, handler, name?)
  Router->>RouteStore: checkDuplicateRoute(method+path)
  Router->>RouteStore: checkDuplicateName(name)
  alt conflict detected and strict mode
    Router->>Exception: throw DuplicateRouteException(details)
  else no conflict or strict mode disabled
    Router->>RouteStore: register route and name
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

I nibble through paths at dawn's first light,
Rewriting URLs to make them right. 🐇
I sniff for twins—a duplicate's plight,
Raise a flag, then hop out of sight.
Hooray for tidy routes tonight! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'adds url-rewriting' describes only a subset of the changes; the PR also introduces duplicate route detection, route name management, and comprehensive testing infrastructure. Revise the title to reflect the primary change more accurately, such as 'Add URL rewriting and duplicate route detection' to better represent the scope of work.
Docstring Coverage ⚠️ Warning Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @README.md:
- Around line 360-365: The README code block showing the duplicate route error
(the block starting with "Duplicate route name detected: 'users'") is missing a
language identifier; update the triple-backtick fence to include a language
specifier (e.g., change ``` to ```text) so the block becomes ```text and ensure
all similar error-code fences in the README follow the same convention for
consistency and linting compliance.
- Around line 266-273: The fenced code block showing the numbered workflow steps
is missing a language identifier; update the opening fence from ``` to ```text
(or ```plaintext) so the block becomes ```text and thus satisfies the linter and
renders correctly (the block containing "1. Client requests:
http://example.com/" through "6. Browser still shows: http://example.com/").
- Around line 341-346: The README code block showing the duplicate route error
should include a language identifier for consistency; change the opening fence
from ``` to ```text so the block reads ```text followed by the message
"Duplicate route detected: GET /users ..." (which references
App\Controllers\UserController@index and App\Controllers\UserController@list)
and keep the closing ```; also scan for any other similar plaintext error blocks
and add the same `text` specifier.

In @src/Routing/Exceptions/DuplicateRouteException.php:
- Around line 57-72: DuplicateRouteException::buildMessage() incorrectly prints
the second route's method/path for both entries; add properties $firstMethod and
$firstPath to the DuplicateRouteException class, assign them when the exception
is constructed (where $firstDefinition is set) from the original route, and then
update buildMessage() to use $this->firstMethod and $this->firstPath for the
"First:" line while keeping $this->routeMethod and $this->routePath for the
"Second:" line so the error shows each route's actual method and path.

In @src/Routing/Router.php:
- Around line 270-281: When detecting a duplicate route name in Router::add (the
block using $name, $this->_registeredNames and throwing
Exceptions\DuplicateRouteException), instead of passing the new route's $method
and $path, extract the original route signature from
$this->_registeredRoutes[$this->_registeredNames[$name]] and parse out its
method and path, then pass those original method/path values into the
DuplicateRouteException so the message reflects the first-registered route; also
update the DuplicateRouteException constructor (and any call sites) to accept
and store the originalMethod and originalPath parameters along with the existing
data and use them in its error message formatting.
🧹 Nitpick comments (1)
src/Routing/Router.php (1)

298-308: Consider enhancing controller info extraction for closures.

The current implementation returns "unknown controller" for closures and other callable types. For better debugging, consider extracting additional information:

Optional enhancement
 protected function extractControllerInfo( RouteMap $route ): string
 {
 	$payload = $route->Payload;

 	if( is_array( $payload ) && isset( $payload['Controller'] ) )
 	{
 		return $payload['Controller'];
 	}

+	if( $payload instanceof \Closure )
+	{
+		$reflection = new \ReflectionFunction( $payload );
+		return sprintf( 'Closure at %s:%d', $reflection->getFileName(), $reflection->getStartLine() );
+	}
+
+	if( is_callable( $payload ) )
+	{
+		return 'callable';
+	}
+
 	return 'unknown controller';
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03d8388 and a484427.

📒 Files selected for processing (4)
  • README.md
  • src/Routing/Exceptions/DuplicateRouteException.php
  • src/Routing/Router.php
  • tests/unit/RouteTest.php
🧰 Additional context used
🧬 Code graph analysis (1)
tests/unit/RouteTest.php (1)
src/Routing/Router.php (2)
  • Router (45-779)
  • clearRoutes (103-113)
🪛 markdownlint-cli2 (0.18.1)
README.md

179-179: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


190-190: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


254-254: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


266-266: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


266-266: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


280-280: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


292-292: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


331-331: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


341-341: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


341-341: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


350-350: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


360-360: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


360-360: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


371-371: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


384-384: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


399-399: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (9)
tests/unit/RouteTest.php (1)

16-21: LGTM! Good test isolation practice.

The tearDown method properly clears routes between tests, which is essential now that strict duplicate route detection is enabled by default. This prevents test pollution and ensures each test starts with a clean router state.

README.md (1)

360-365: Documentation shows error format that differs from implementation.

The documented error message shows different HTTP methods and paths for the first and second routes (GET /users vs POST /users/create), but the current DuplicateRouteException implementation displays the same method and path for both entries. See my comment on DuplicateRouteException.php for details on the bug.

src/Routing/Exceptions/DuplicateRouteException.php (2)

74-84: LGTM for method+path duplicate messages.

This branch correctly handles duplicates where the method and path are identical—displaying the same values for both is accurate in this case.


91-134: LGTM! Clean accessor methods.

The getter methods are well-documented and follow standard patterns.

src/Routing/Router.php (5)

56-66: LGTM! Well-documented tracking properties.

The new properties for duplicate detection and URL rewrites are clearly documented with their purpose and structure.


89-113: LGTM! Clean implementation of strict mode and route clearing.

Both methods work correctly. Note that clearRoutes() intentionally preserves _filterRegistry since filters are registered separately from routes—this is appropriate behavior.


150-174: LGTM! Robust URL rewrite handling.

The implementation correctly handles edge cases:

  • Root "/" is preserved (not stripped to empty string)
  • Both /path and /path/ variants are checked
  • Debug logging aids troubleshooting

317-356: LGTM! Consistent HTTP method propagation.

All route registration methods correctly pass their respective HTTP method strings to addRoute(), enabling proper duplicate detection per method+path combination.


650-651: LGTM! Correct integration point for URL rewrites.

Rewrites are applied after input validation and before route matching, which aligns with the documented workflow. The original URL from $argv['route'] is still used in the 404 warning log, which is appropriate for debugging.

@codecov
Copy link

codecov bot commented Jan 12, 2026

Codecov Report

❌ Patch coverage is 62.81407% with 74 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/Routing/Router.php 58.57% 58 Missing ⚠️
src/Routing/Exceptions/DuplicateRouteException.php 70.21% 14 Missing ⚠️
src/Routing/RouteMap.php 83.33% 2 Missing ⚠️
Flag Coverage Δ Complexity Δ
routing 79.56% <62.81%> (-3.90%) 351.00 <60.00> (+52.00) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ Complexity Δ
src/Routing/RouteMap.php 96.84% <83.33%> (-1.97%) 35.00 <6.00> (+5.00) ⬇️
src/Routing/Exceptions/DuplicateRouteException.php 70.21% <70.21%> (ø) 10.00 <10.00> (?)
src/Routing/Router.php 80.37% <58.57%> (-17.47%) 112.00 <44.00> (+37.00) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @src/Routing/RouteMap.php:
- Around line 218-228: The setName method can throw DuplicateRouteException if
called twice with the same name because it re-registers unconditionally; update
setName (method setName) to first check if $this->Name is already set and equals
$name and in that case simply return $this without calling
$this->_router->registerRouteName; otherwise proceed to register the new name
(and optionally, if you support renaming, call a router method to unregister the
old name before registering the new one), ensuring you still reference
$this->_router, $this->_method, $this->Path and $this->Name appropriately.

In @src/Routing/Router.php:
- Around line 225-253: The route name is registered via RouteMap::setName before
checkDuplicateRoute runs, so if checkDuplicateRoute throws the name remains in
the router's _registeredNames; to fix, perform the path+method duplicate check
(this->checkDuplicateRoute($route, $method)) before calling
$route->setName($name) when _strictMode is enabled, or alternately wrap setName
in a try/catch and unregister the name on exception; update addRoute accordingly
(function addRoute, RouteMap::setName, checkDuplicateRoute, _strictMode, and any
_registeredNames cleanup) so a thrown duplicate-path error never leaves a stale
registered name.
🧹 Nitpick comments (1)
src/Routing/Router.php (1)

332-348: Consider enhancing controller extraction for better error messages.

Since Payload is typically set after route creation, extractControllerInfo() will likely return "unknown controller" for the conflicting route. For more informative error messages, consider extracting the callable information directly from $route->Function.

♻️ Optional enhancement
 protected function extractControllerInfo( RouteMap $route ): string
 {
 	$payload = $route->Payload;

 	if( is_array( $payload ) && isset( $payload['Controller'] ) )
 	{
 		return $payload['Controller'];
 	}

+	// Try to get info from the callable
+	$function = $route->Function;
+	if( is_array( $function ) && count( $function ) === 2 )
+	{
+		$class = is_object( $function[0] ) ? get_class( $function[0] ) : $function[0];
+		return $class . '@' . $function[1];
+	}
+
 	return 'unknown controller';
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 804fc2e and 2d02ee1.

📒 Files selected for processing (3)
  • src/Routing/RouteMap.php
  • src/Routing/Router.php
  • tests/unit/RouterTest.php
🧰 Additional context used
🧬 Code graph analysis (2)
src/Routing/RouteMap.php (1)
src/Routing/Router.php (2)
  • Router (45-823)
  • registerRouteName (269-298)
src/Routing/Router.php (3)
src/Routing/RouteMap.php (2)
  • RouteMap (40-333)
  • getPath (104-107)
src/Routing/Exceptions/DuplicateRouteException.php (1)
  • DuplicateRouteException (15-163)
src/Routing/Request.php (1)
  • getPath (50-53)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (16)
src/Routing/RouteMap.php (2)

49-53: LGTM! Router context properties for duplicate detection.

The nullable private properties appropriately store the router reference and HTTP method needed for registering route names during duplicate detection.


88-99: LGTM! Clean fluent API for setting router context.

The method correctly stores the router reference and method, enabling RouteMap to register names with the router for duplicate detection. Since this is called internally by Router::addRoute() with controlled method strings, additional validation is unnecessary.

tests/unit/RouterTest.php (5)

647-656: LGTM! Good test coverage for duplicate path detection.

The test correctly validates that registering two routes with identical method and path throws DuplicateRouteException.


658-670: LGTM! Validates fluent API name conflict detection.

Test correctly verifies that duplicate route names via the fluent setName() API throw an exception with the route name in the message.


672-682: LGTM! Tests name parameter API for duplicates.

Correctly validates that passing duplicate names via the route method's $name parameter also triggers the exception.


684-696: LGTM! Important edge case for cross-method name conflicts.

Correctly validates that route names must be unique across all HTTP methods, not just within the same method.


698-714: LGTM! Good coverage for strict mode disabled behavior.

The test correctly validates that when strict mode is disabled, duplicate routes and names are silently allowed without throwing exceptions.

src/Routing/Router.php (9)

56-66: LGTM! Well-structured tracking properties.

Good design choices: strict mode defaults to true for safety, and the data structures clearly separate route path tracking from name tracking.


79-92: LGTM! Clean strict mode setter.

The method and its documentation clearly explain the behavior difference between strict and non-strict modes.


94-113: LGTM! Comprehensive state reset for testing.

The method correctly resets all route-related state. The _filterRegistry (registered Filter instances) is intentionally preserved, as filters are typically registered once at application bootstrap, separate from route configuration.


115-138: LGTM! Well-documented URL rewrite configuration.

The method and docblock clearly explain the use case and provide helpful examples.


140-174: LGTM! Clean URL rewrite implementation.

The method correctly handles trailing slash normalization and falls back gracefully when no rewrite rule matches. The "exact matches only" limitation is clearly documented.


255-298: LGTM! Robust name registration with detailed error reporting.

The method correctly handles both strict and non-strict modes, and provides useful debugging information in the exception when a duplicate name is detected.


300-330: LGTM! Clean duplicate route detection.

The method correctly checks for existing method+path combinations and registers the new route only after validation passes.


350-400: LGTM! Clean API extension for route naming.

The HTTP method helpers consistently accept an optional $name parameter while maintaining backward compatibility. The explicit method strings ('GET', 'POST', etc.) correctly match the expected format for signature creation.


694-696: LGTM! Clean integration of URL rewrites.

The rewrite is correctly applied after extracting the URI and before route matching, making it transparent to downstream routing logic.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In @src/Routing/RouteMap.php:
- Around line 212-241: setName() currently unregisters the old route name before
calling registerRouteName(), which can leave the router missing the old name if
registerRouteName() throws; change the logic to attempt registering the new name
first (call _router->registerRouteName($name, $this->_method, $this->Path,
$this)) and only if that succeeds unregister the old name (or, alternatively,
wrap the registration in a try/catch and on failure re-register the old name),
then update $this->Name and return; ensure this applies only when _router and
_method are set and keep the early-return when $this->Name === $name.

In @src/Routing/Router.php:
- Around line 94-113: clearRoutes() currently claims to clear filters but does
not reset $_filterRegistry; update the method to also clear the filter registry
by resetting $this->_filterRegistry = [] (or, if intended, modify the docblock
to state it only clears the active filter list $_filter and routing state).
Locate the clearRoutes method and either add the reset for $_filterRegistry
alongside $_filter, or change the docblock text to remove/clarify the statement
about clearing filters so it matches the actual fields cleared.
- Around line 79-92: setStrictMode currently just flips _strictMode, but
existing routes added while strict was false can create undetected duplicates;
update setStrictMode so that when $strict is true it rebuilds or re-checks
duplicate tracking for existing routes (iterate $this->_routes and re-populate /
re-validate whatever index you use, e.g. _routeIndex) and call
checkDuplicateRoute or throw DuplicateRouteException for any duplicates found;
reference setStrictMode, addRoute, checkDuplicateRoute, _strictMode, _routes,
_routeIndex and DuplicateRouteException when implementing this fix.
- Around line 140-174: The debug call in rewriteUrl() logs the raw incoming $url
which can include query strings or sensitive tokens; update rewriteUrl to log
only the path portion (strip the query string and fragment using parse_url or by
truncating at '?'/#) or log the normalized path from $normalizedUrl instead of
the full $url, and change the Log::debug messages (in the branches referencing
$this->_urlRewrites[$normalizedUrl] and $this->_urlRewrites[$url]) to output
only the sanitized path or a redacted placeholder so query params and tokens are
not emitted.
- Around line 257-365: The registries must be updated consistently while
retaining “first match wins” and only throwing in strict mode: update
checkDuplicateRoute(RouteMap $route, string $method) to always register the
signature in $_registeredRoutes (i.e. set $_registeredRoutes[$signature] =
extractControllerInfo($route)) but only throw Exceptions\DuplicateRouteException
when $_strictMode is true (use existing isset check but gate the throw on
$_strictMode); change registerRouteName(string $name, ...) so that in non-strict
mode it does not overwrite an existing $_registeredNames[$name] (leave the first
registered "{$method}:{$path}" intact), and in strict mode keep the current
duplicate-detection/throw behavior; ensure both methods use the same signature
format ("{$method}:{$path}") and reference extractControllerInfo($route) when
registering.
- Around line 215-255: addRoute currently only populates $_registeredRoutes when
$_strictMode is true and leaves an entry if setName() throws, causing stale
signatures; change addRoute so it always registers the route signature (call or
replicate checkDuplicateRoute logic to add the method:path to $_registeredRoutes
regardless of $_strictMode) before attempting setName(), then wrap setName() in
a try/catch and remove/unregister that exact signature from $_registeredRoutes
if setName() throws so you don't leave stale entries; reference addRoute,
checkDuplicateRoute, setName, $_registeredRoutes, $_strictMode and ensure
rollback/unregister happens on name failure.
🧹 Nitpick comments (3)
src/Routing/RouteMap.php (1)

88-99: Consider normalizing/validating $method in setRouterContext() (defensive).
If a caller ever passes get vs GET, you’ll end up with inconsistent method:path signatures.

src/Routing/Router.php (2)

115-138: setUrlRewrites() looks good; consider normalizing rules on set (optional).
If you normalize keys/values once here (leading slash, trailing slash), rewriteUrl() can stay simpler and behavior becomes easier to reason about.


710-712: Good: rewrites are applied before matching; consider logging original + rewritten for debugging (optional).
Right now later logs/warnings use the original route in some places, which can be confusing when a rewrite is active.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d02ee1 and 8063b59.

📒 Files selected for processing (2)
  • src/Routing/RouteMap.php
  • src/Routing/Router.php
🧰 Additional context used
🧬 Code graph analysis (2)
src/Routing/RouteMap.php (1)
src/Routing/Router.php (3)
  • Router (45-839)
  • unregisterRouteName (311-314)
  • registerRouteName (271-300)
src/Routing/Router.php (2)
src/Routing/RouteMap.php (3)
  • RouteMap (40-346)
  • setRouterContext (94-99)
  • getPath (104-107)
src/Routing/Exceptions/DuplicateRouteException.php (1)
  • DuplicateRouteException (15-163)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (3)
src/Routing/RouteMap.php (1)

49-54: Good: route-scoped router context is private and nullable.
No concerns with introducing RouteMap::$_router / RouteMap::$_method for name registration.

src/Routing/Router.php (2)

56-67: Good: explicit registries for duplicates and rewrites.
The state buckets ($_registeredRoutes, $_registeredNames, $_urlRewrites, $_strictMode) are a clean basis for the new behavior.


366-416: Good: optional ?string $name stays BC-friendly on method helpers.
Parameter is appended and defaults to null, so existing call sites remain valid.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/Routing/Router.php (2)

935-963: Consider validating parameter substitution.

The generateUrl() method doesn't verify that all route parameters (:param) were substituted. If a required parameter is missing from the $parameters array, the generated URL will contain the literal :param placeholder.

This may be intentional for flexibility, but consider either:

  1. Logging a warning when unsubstituted parameters remain
  2. Throwing an exception for missing required parameters
♻️ Optional: Add validation for complete substitution
 public function generateUrl( string $name, array $parameters = [], bool $absolute = false ): ?string
 {
     $route = $this->getRouteByName( $name );
     
     if( !$route )
     {
         return null;
     }

     $path = $route->getPath();
     
     // Replace route parameters with actual values
     foreach( $parameters as $key => $value )
     {
         $path = str_replace( ':' . $key, $value, $path );
     }

+    // Warn if unsubstituted parameters remain
+    if( preg_match( '/:(\w+)/', $path, $matches ) )
+    {
+        Log::warning( "generateUrl: Missing parameter '{$matches[1]}' for route '{$name}'" );
+    }
+
     // If absolute URL requested, prepend base URL

907-925: Consider using the name registry for O(1) lookup.

getRouteByName() iterates through all routes for O(n) lookup. Since $_registeredNames already maps names to signatures, you could use it for direct lookup, then fetch the route from the appropriate method array.

This is a minor optimization and the current implementation is fine for typical route counts.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8063b59 and fdd3815.

📒 Files selected for processing (2)
  • src/Routing/RouteMap.php
  • src/Routing/Router.php
🧰 Additional context used
🧬 Code graph analysis (2)
src/Routing/RouteMap.php (1)
src/Routing/Router.php (3)
  • Router (45-998)
  • registerRouteName (421-449)
  • unregisterRouteName (460-463)
src/Routing/Router.php (2)
src/Routing/RouteMap.php (3)
  • RouteMap (40-349)
  • setRouterContext (94-99)
  • setName (218-244)
src/Routing/Exceptions/DuplicateRouteException.php (1)
  • DuplicateRouteException (15-163)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (17)
src/Routing/RouteMap.php (3)

49-53: LGTM!

The nullable properties for router context are appropriately typed and provide a clean mechanism for context propagation without breaking existing RouteMap usage.


88-99: LGTM!

Clean fluent interface method for setting router context. The implementation correctly stores both router reference and HTTP method for subsequent name registration operations.


218-244: Well-structured rollback-safe name registration.

The implementation correctly registers the new name before unregistering the old one, ensuring atomicity—if registration fails due to a duplicate, the old name remains intact. The early return optimization for unchanged names is a good touch.

One edge case to consider: if setName() is called on a RouteMap before setRouterContext() is set, the name will be stored locally but not registered with the router for duplicate detection. If setRouterContext() is later called, the existing name won't be retroactively registered. This appears acceptable given the intended usage flow in Router::addRoute(), but verify this doesn't cause issues if RouteMap is used standalone.

src/Routing/Router.php (14)

56-66: LGTM!

The new properties are well-typed and documented. Defaulting $_strictMode to true is a safe choice that encourages explicit route definitions.


79-102: LGTM!

Good defensive design: validating existing routes when enabling strict mode ensures that enabling strict mode doesn't leave the router in an inconsistent state if duplicates already exist.


113-188: LGTM!

The rollback mechanism is well-implemented—saving registry state before validation and restoring on failure ensures atomicity. The iteration through all HTTP method arrays provides comprehensive duplicate detection.


199-210: LGTM!

Complete reset of route-related state. Note that $_strictMode and $_ipResolver are intentionally preserved, which makes sense as they're configuration/dependency settings rather than route state.


232-235: LGTM!

Clean setter with good documentation. The simple assignment is appropriate here.


247-274: LGTM!

The method correctly handles trailing slash normalization while preserving the ability to define rewrites for both forms. Returning the original URL when no rewrite matches preserves existing behavior.


285-313: Good security practice.

Sanitizing URLs before logging prevents accidental exposure of sensitive query parameters (tokens, API keys, etc.). The implementation correctly handles all combinations of query strings and fragments.


364-401: LGTM!

The rollback mechanism in the try-catch ensures consistency: if setName() throws a DuplicateRouteException, the route signature added by checkDuplicateRoute() is properly removed before re-throwing. This prevents orphaned registry entries.


421-449: LGTM!

The method correctly implements first-match-wins semantics in non-strict mode and throws descriptive exceptions in strict mode. The explode( ':', $originalSignature, 2 ) correctly handles route paths that contain colons.


460-463: LGTM!

Simple and safe—unset() on a non-existent key is a no-op in PHP, so no additional checks are needed.


477-505: LGTM!

The first-match-wins semantics are correctly implemented: in non-strict mode, only the first occurrence is registered in the tracking registry, which aligns with the runtime routing behavior where routes are matched in registration order.


513-523: LGTM!

Simple extraction method with a sensible fallback for error message generation.


533-575: LGTM!

Consistent API extension across all HTTP method helpers. The optional $name parameter maintains backward compatibility while enabling named route registration.


869-871: LGTM!

Clean integration of URL rewriting before route matching. The comment clearly documents the purpose.

@ljonesfl ljonesfl closed this Jan 12, 2026
@ljonesfl ljonesfl deleted the feature/url-rewriting branch January 12, 2026 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant