Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Route freezing: routes become immutable after `prepareRoutes()`, preventing silent post-compilation misconfiguration.
- `FreezeableInterface`, `FreezeableTrait`, and `FreezeGuard` for route immutability lifecycle.
- Optional segment support in `generateUrl()`: routes using FastRoute `[/{param}]` syntax resolve correctly with defaults from `setVars()` or omit the segment when unset.
- `query()` route helper on `Router` and `RouteGroup` (via `RouteCollectionInterface`) for the HTTP QUERY method (draft-ietf-httpbis-safe-method-w-body).

### Changed
- Minimum PHP version raised to 8.3.
Expand Down Expand Up @@ -59,6 +60,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- `Dispatcher::setRouteMap()` removed (route map now set via constructor).
- Duplicate `Router::processGroups()` method removed (consolidated into `collectGroupRoutes()`).

### Fixed
- Naming a route no longer alters route registration order, which could cause a `FastRoute\BadRouteException` when a named static route was shadowed by a later variable route (#362, #363).

## [6.2.0] 2024-11

### Changed
Expand Down
3 changes: 3 additions & 0 deletions docs/unstable/routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ $router->patch('/acme/route', 'Acme\Controller::patchMethod');
$router->delete('/acme/route', 'Acme\Controller::deleteMethod');
$router->head('/acme/route', 'Acme\Controller::headMethod');
$router->options('/acme/route', 'Acme\Controller::optionsMethod');
$router->query('/acme/route', 'Acme\Controller::queryMethod');
~~~

Each of the above routes will respond to the same URI but will invoke a different callable based on the HTTP request method.

The `query` verb registers a route for the HTTP QUERY method, a safe, idempotent method that, unlike GET, permits a request body.

## Route Conditions

There are times when you may wish to add further conditions to route matching other than the request verb and URI. Route allows this by chaining further conditions to the route definition.
Expand Down
1 change: 1 addition & 0 deletions src/Http/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ final class Request
public const string METHOD_DELETE = 'DELETE';
public const string METHOD_HEAD = 'HEAD';
public const string METHOD_OPTIONS = 'OPTIONS';
public const string METHOD_QUERY = 'QUERY';
}
3 changes: 3 additions & 0 deletions src/RouteCollectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,7 @@ public function post(string $path, callable|array|string|RequestHandlerInterface

/** @param callable|array<string>|string|RequestHandlerInterface $handler */
public function put(string $path, callable|array|string|RequestHandlerInterface $handler): Route;

/** @param callable|array<string>|string|RequestHandlerInterface $handler */
public function query(string $path, callable|array|string|RequestHandlerInterface $handler): Route;
}
6 changes: 6 additions & 0 deletions src/RouteCollectionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,10 @@ public function put(string $path, callable|array|string|RequestHandlerInterface
{
return $this->map(Request::METHOD_PUT, $path, $handler);
}

/** @param callable|array<string>|string|RequestHandlerInterface $handler */
public function query(string $path, callable|array|string|RequestHandlerInterface $handler): Route
{
return $this->map(Request::METHOD_QUERY, $path, $handler);
}
}
31 changes: 30 additions & 1 deletion tests/RouterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
declare(strict_types=1);

use League\Route\MatchStatus;
use League\Route\Route;
use League\Route\Router;
use League\Route\Strategy\ApplicationStrategy;
use Mockery\MockInterface;
Expand All @@ -14,14 +15,22 @@
$path = '/something';
$callable = function () {};

foreach (['get', 'post', 'put', 'patch', 'delete', 'head', 'options'] as $method) {
foreach (['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'query'] as $method) {
$route = $router->map($method, $path, $callable);
expect($route->getMethod())->toBe($method);
expect($route->getPath())->toBe($path);
expect($route->getCallable())->toBe($callable);
}
});

test('query verb helper returns route with QUERY method', function () {
$router = new Router();
$route = $router->query('/path', fn() => null);

expect($route)->toBeInstanceOf(Route::class);
expect($route->getMethod())->toBe('QUERY');
});

test('router maps and returns a route group with correct prefix', function () {
$router = new Router();
$prefix = '/something';
Expand Down Expand Up @@ -151,6 +160,26 @@
expect($routes[2]->getPath())->toBe('/baz');
});

test('naming a static route does not let a later variable route shadow it', function () {
/** @var UriInterface&MockInterface $uri */
$uri = Mockery::mock(UriInterface::class);
$uri->allows('getPath')->andReturn('/some-path');

/** @var ServerRequestInterface&MockInterface $request */
$request = Mockery::mock(ServerRequestInterface::class);
$request->allows('getMethod')->andReturn('GET');
$request->allows('getUri')->andReturn($uri);

$router = new Router();
$router->map('GET', '/some-path', static function () {})->setName('someName');
$router->map('GET', '/{path}', static function () {});

$result = $router->match($request);

expect($result->isFound())->toBeTrue();
expect($result->getStatus())->toBe(MatchStatus::Found);
});

test('getRoutes includes routes registered inside a group', function () {
$router = new Router();
$router->get('/top', static function () {});
Expand Down
Loading