Skip to content

v5 - Refactor routing to use RouteMatch and dispatcher interfaces#3438

Merged
odan merged 1 commit intoslimphp:5.xfrom
odan:5.x-route-arguments
Apr 3, 2026
Merged

v5 - Refactor routing to use RouteMatch and dispatcher interfaces#3438
odan merged 1 commit intoslimphp:5.xfrom
odan:5.x-route-arguments

Conversation

@odan
Copy link
Copy Markdown
Contributor

@odan odan commented Apr 3, 2026

This is a routing architecture refactor that replaces request attributes based on RouteContext::ROUTING_RESULTS with RouteMatch::class

Changes

  • Introduce DispatcherInterface and add FastRouteDispatcher as the default dispatcher implementation
  • Register DispatcherInterface in container definitions and resolve it via FastRouteDispatcher
  • Introduce RouteInterface and update Route to implement it
  • Add RouteMatch as the new immutable routing result object for found, not found, and method-not-allowed states
  • Refactor RoutingMiddleware to use injected dispatcher and attach RouteMatch to the request
  • Refactor EndpointMiddleware to consume RouteMatch instead of RoutingResults
  • Update RoutingArgumentsMiddleware to read route arguments from RouteMatch
  • Remove legacy RoutingResults and RouteContext classes
  • Move endpoint execution to interface-based route handling with improved validation and error messages
  • Refactor route middleware collection to preserve parent group to route-specific execution order
  • Add route argument storage and accessors to Route via getArgument(), getArguments(), and setArguments()
  • Add license headers and small cleanup improvements in routing-related classes
  • Update tests to validate RouteMatch behavior and replace legacy routing context/result assertions
  • Remove outdated tests for RouteContext and RoutingResults
  • Update changelog by removing the deprecation/removal note for setArguments and setArgument
  • Adjust base path handling to rely on router state instead of request route context attributes

@odan odan requested a review from Copilot April 3, 2026 14:31
@coveralls
Copy link
Copy Markdown

Coverage Status

coverage: 96.162% (-0.8%) from 96.939%
when pulling 24993e5 on odan:5.x-route-arguments
into ad393ae on slimphp:5.x.

@odan odan merged commit 2f558e3 into slimphp:5.x Apr 3, 2026
6 of 7 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refactors Slim v5 routing to replace request-attribute based RouteContext::ROUTING_RESULTS / RoutingResults with an immutable RouteMatch object and interface-driven dispatching, updating middleware and tests accordingly.

Changes:

  • Introduces DispatcherInterface with a default FastRouteDispatcher, and wires it into container definitions.
  • Adds RouteInterface and updates Route + routing/endpoint middleware to operate on RouteMatch attached to the request.
  • Replaces legacy routing context/results tests with RouteMatch-focused tests and removes deprecated classes/tests.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/Routing/RoutingResultsTest.php Removes legacy RoutingResults test coverage.
tests/Routing/RouteMatchTest.php Adds tests for the new RouteMatch immutable routing result.
tests/Routing/RouteContextTest.php Removes legacy RouteContext test coverage.
tests/Middleware/RoutingMiddlewareTest.php Updates middleware tests to assert RouteMatch request attribute behavior.
tests/Middleware/BasePathMiddlewareTest.php Updates base path assertions to use app/router state rather than request attributes.
Slim/Routing/RoutingResults.php Removes legacy routing result object.
Slim/Routing/RouteMatch.php Adds the new immutable routing result object.
Slim/Routing/RouteContext.php Removes legacy routing context helper.
Slim/Routing/Route.php Implements RouteInterface and adds route argument storage accessors.
Slim/Routing/FastRouteDispatcher.php Adds default dispatcher implementation backed by FastRoute.
Slim/Middleware/RoutingMiddleware.php Refactors routing middleware to use injected dispatcher and attach RouteMatch.
Slim/Middleware/RoutingArgumentsMiddleware.php Reads route arguments from RouteMatch and attaches them to request attributes.
Slim/Middleware/EndpointMiddleware.php Executes endpoints based on RouteMatch and refactors middleware collection ordering.
Slim/Middleware/BasePathMiddleware.php Shifts base path handling to router state (removing request attribute use).
Slim/Interfaces/RouteInterface.php Introduces a route abstraction consumed by routing components.
Slim/Interfaces/DispatcherInterface.php Introduces a dispatcher abstraction for route matching.
Slim/Container/Definition/SlimDefinitions.php Registers DispatcherInterface in the container using FastRouteDispatcher.
CHANGELOG.md Updates changelog entries related to removed/deprecated routing APIs.
Comments suppressed due to low confidence (1)

Slim/Middleware/RoutingMiddleware.php:127

  • stripBasePath() blindly strips strlen($basePath) bytes from $uri without verifying that $uri actually starts with $basePath (or that $basePath is shorter than $uri). With a misconfigured base path this can turn unrelated paths into '/' and route incorrectly. Guard with a prefix check (and only strip when it matches), otherwise return the original $uri.
    private function stripBasePath(string $uri, string $basePath): string
    {
        // No base path configured
        if ($basePath === '' || $basePath === '/') {
            return $uri;
        }

        return '/' . ltrim(rtrim(substr($uri, strlen($basePath)), '/'), '/');
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

*
* @var array<string, string>
*/
private array $arguments;
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

$arguments is a typed property but never initialized (no default value and not set in the constructor). Calling getArgument()/getArguments() before setArguments() will throw "Typed property must not be accessed before initialization". Initialize it to an empty array (e.g., default [] on the property or set it in the constructor).

Suggested change
private array $arguments;
private array $arguments = [];

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +75
$requestPath = $request->getUri()->getPath();
$basePath = $this->router->getBasePath();
$dispatchPath = $this->stripBasePath($requestPath, $this->router->getBasePath());

$httpMethod = $request->getMethod();
$uri = $request->getUri()->getPath();
$routingResult = $this->dispatcher->dispatch(
$request->getMethod(),
rawurldecode($dispatchPath)
);

// Determine base path
$basePath = $request->getAttribute(RouteContext::BASE_PATH) ?? $this->router->getBasePath();
$routeMatch = $this->createRouteMatch($routingResult, $basePath);
$request = $request->withAttribute(RouteMatch::class, $routeMatch);

return $handler->handle($request);
}

/**
* @param array<int, mixed> $routingResult
*/
private function createRouteMatch(array $routingResult, string $basePath): RouteMatch
{
$status = $routingResult[0] ?? null;

return match ($status) {
DispatcherInterface::FOUND => RouteMatch::found(
$this->assertRoute($routingResult[1] ?? null),
$this->extractArguments($routingResult[2] ?? null),
),
DispatcherInterface::METHOD_NOT_ALLOWED => RouteMatch::methodNotAllowed(
$this->extractAllowedMethods($routingResult[1] ?? null),
),
DispatcherInterface::NOT_FOUND => RouteMatch::notFound(),
default => throw new RuntimeException('Invalid routing result status returned by dispatcher.'),
};
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

createRouteMatch() takes $basePath, but the value is never used inside the method (and the caller computes/passes it anyway). Either remove this parameter (and related $basePath local) or incorporate base path into the routing result object if that’s still required by the new design.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to 49
// $request = $request->withAttribute(RouteMatch::BASE_PATH_ATTRIBUTE, $basePath);

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

There’s a commented-out line referencing RouteMatch::BASE_PATH_ATTRIBUTE, but RouteMatch does not define that constant. Leaving dead/commented code with invalid identifiers makes the intent unclear—either remove this line or implement the attribute/constant as part of the new base-path strategy.

Suggested change
// $request = $request->withAttribute(RouteMatch::BASE_PATH_ATTRIBUTE, $basePath);

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +47
$basePath = '/api';

$routeMatch = RouteMatch::notFound($basePath);

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

RouteMatch::notFound() is called with $basePath, but RouteMatch::notFound() is defined with no parameters. This test will fatal; either update the factory signature/implementation to accept base path (if intended) or remove the argument from the test.

Suggested change
$basePath = '/api';
$routeMatch = RouteMatch::notFound($basePath);
$routeMatch = RouteMatch::notFound();

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +62
$basePath = '/api';

$routeMatch = RouteMatch::methodNotAllowed($allowedMethods, $basePath);
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

RouteMatch::methodNotAllowed() is called with ($allowedMethods, $basePath), but RouteMatch::methodNotAllowed() only accepts the allowed-methods array. This mismatch will fatal at runtime; align the test with the factory signature, or update RouteMatch if base path is meant to be carried in the match object.

Suggested change
$basePath = '/api';
$routeMatch = RouteMatch::methodNotAllowed($allowedMethods, $basePath);
$routeMatch = RouteMatch::methodNotAllowed($allowedMethods);

Copilot uses AI. Check for mistakes.
use Slim\Middleware\RoutingMiddleware;
use Slim\Routing\RouteContext;
use Slim\Routing\RouteMatch;
use Slim\Routing\RoutingResults;
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

use Slim\\Routing\\RoutingResults; is now unused and refers to a class removed by this PR. While it won’t autoload, it’s confusing and can cause IDE/static-analysis noise—please remove the import.

Suggested change
use Slim\Routing\RoutingResults;

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +77
/**
* Retrieve a specific route argument.
*/
public function getArgument(string $name, ?string $default = null): ?string;

/**
* Get route arguments.
*
* @return array<string, string>
*/
public function getArguments(): array;

/**
* Set route arguments.
*
* @param array<string,mixed> $arguments The arguments.
*
* @return RouteInterface
*/
public function setArguments(array $arguments): RouteInterface;
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The RouteInterface argument APIs are internally inconsistent: getArgument()/getArguments() are documented/typed as returning strings, but setArguments() documents array<string, mixed>. Either restrict setArguments() to array<string, string> (and enforce/validate) or widen the getters’ types/docs so callers aren’t misled.

Copilot uses AI. Check for mistakes.
@odan odan deleted the 5.x-route-arguments branch April 3, 2026 17:09
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.

3 participants