Skip to content
Open
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
9 changes: 9 additions & 0 deletions src/Illuminate/Contracts/Routing/Registrar.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ public function patch($uri, $action);
*/
public function options($uri, $action);

/**
* Register a new QUERY route with the router.
*
* @param string $uri
* @param array|string|callable $action
* @return \Illuminate\Routing\Route
*/
public function query($uri, $action);

/**
* Register a new route with the given verbs.
*
Expand Down
3 changes: 2 additions & 1 deletion src/Illuminate/Foundation/Console/RouteListCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class RouteListCommand extends Command
'GET' => 'blue',
'HEAD' => '#6C7280',
'OPTIONS' => '#6C7280',
'QUERY' => '#6C7280',
'POST' => 'yellow',
'PUT' => 'yellow',
'PATCH' => 'yellow',
Expand Down Expand Up @@ -394,7 +395,7 @@ protected function forCli($routes)
$routes = $routes->map(
fn ($route) => array_merge($route, [
'action' => $this->formatActionForCli($route),
'method' => $route['method'] == 'GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS' ? 'ANY' : $route['method'],
'method' => $route['method'] == implode('|', Router::$verbs) ? 'ANY' : $route['method'],
'uri' => $route['domain'] ? ($route['domain'].'/'.ltrim($route['uri'], '/')) : $route['uri'],
]),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public function handle($request, Closure $next)
*/
protected function isReading($request)
{
return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']);
return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS', 'QUERY']);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/Illuminate/Routing/RouteRegistrar.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* @method \Illuminate\Routing\Route patch(string $uri, \Closure|array|string|null $action = null)
* @method \Illuminate\Routing\Route post(string $uri, \Closure|array|string|null $action = null)
* @method \Illuminate\Routing\Route put(string $uri, \Closure|array|string|null $action = null)
* @method \Illuminate\Routing\Route query(string $uri, \Closure|array|string|null $action = null)
* @method \Illuminate\Routing\RouteRegistrar as(string $value)
* @method \Illuminate\Routing\RouteRegistrar can(\UnitEnum|string $ability, array|string $models = [])
* @method \Illuminate\Routing\RouteRegistrar controller(string $controller)
Expand Down Expand Up @@ -60,7 +61,7 @@ class RouteRegistrar
* @var string[]
*/
protected $passthru = [
'get', 'post', 'put', 'patch', 'delete', 'options', 'any',
'get', 'post', 'put', 'patch', 'delete', 'options', 'query', 'any',
];

/**
Expand Down
14 changes: 13 additions & 1 deletion src/Illuminate/Routing/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class Router implements BindingRegistrar, RegistrarContract
*
* @var string[]
*/
public static $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
public static $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'QUERY'];

/**
* Create a new Router instance.
Expand Down Expand Up @@ -220,6 +220,18 @@ public function options($uri, $action = null)
return $this->addRoute('OPTIONS', $uri, $action);
}

/**
* Register a new QUERY route with the router.
*
* @param string $uri
* @param array|string|callable|null $action
* @return \Illuminate\Routing\Route
*/
public function query($uri, $action = null)
{
return $this->addRoute('QUERY', $uri, $action);

This comment was marked as resolved.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I don’t believe HEAD should be added here

Laravel’s get() registers HEAD because RFC 9110 defines HEAD as identical to GET except no response body. RFC 10008 defines QUERY as its own method whose semantics depend on request content, so a HEAD request would not be equivalent to a QUERY request.

This also keeps it consistent with options(), which is safe but does not automatically register HEAD. Route::any() still includes both HEAD and QUERY through Router::$verbs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Gotcha! Thanks for the explanation 👍🏻

}

/**
* Register a new route responding to all verbs.
*
Expand Down
1 change: 1 addition & 0 deletions src/Illuminate/Support/Facades/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* @method static \Illuminate\Routing\Route patch(string $uri, array|string|callable|null $action = null)
* @method static \Illuminate\Routing\Route delete(string $uri, array|string|callable|null $action = null)
* @method static \Illuminate\Routing\Route options(string $uri, array|string|callable|null $action = null)
* @method static \Illuminate\Routing\Route query(string $uri, array|string|callable|null $action = null)
* @method static \Illuminate\Routing\Route any(string $uri, array|string|callable|null $action = null)
* @method static \Illuminate\Routing\Route fallback(array|string|callable|null $action)
* @method static \Illuminate\Routing\Route redirect(string $uri, string $destination, int $status = 302)
Expand Down
14 changes: 12 additions & 2 deletions tests/Http/Middleware/PreventRequestForgeryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ public function test_same_origin_header_passes()
$this->assertSame('OK', $response->getContent());
}

public function test_query_requests_pass_without_token()
{
$middleware = $this->createMiddleware();
$request = $this->createRequest(method: 'QUERY');

$response = $middleware->handle($request, fn () => new Response('OK'));

$this->assertSame('OK', $response->getContent());
}

public function test_same_site_header_rejected_by_default()
{
$middleware = $this->createMiddleware();
Expand Down Expand Up @@ -121,11 +131,11 @@ public function test_origin_only_mode_passes_same_origin()
$this->assertSame('OK', $response->getContent());
}

protected function createRequest(array $server = [], ?string $token = null)
protected function createRequest(array $server = [], ?string $token = null, string $method = 'POST')
{
$request = Request::create(
'http://example.com/test',
'POST',
$method,
$token ? ['_token' => $token] : [],
[],
[],
Expand Down
11 changes: 11 additions & 0 deletions tests/Integration/Routing/Fixtures/query_routes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

use Illuminate\Support\Facades\Route;

Route::query('/search', function () {
return response()->json([
'method' => request()->method(),
'term' => request()->query('term'),
'filter' => request()->input('filter'),
]);
});
11 changes: 11 additions & 0 deletions tests/Integration/Routing/RouteCachingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ public function testRedirectRoutes()
$this->get('/foo/1')->assertRedirect('/foo/1/bar');
}

public function testQueryRoutes()
{
$this->routes(__DIR__.'/Fixtures/query_routes.php');

$this->call('QUERY', '/search?term=laravel', ['filter' => 'framework'])->assertExactJson([
'method' => 'QUERY',
'term' => 'laravel',
'filter' => 'framework',
]);
}

protected function routes(string $file)
{
$this->defineCacheRoutes(file_get_contents($file));
Expand Down
11 changes: 11 additions & 0 deletions tests/Routing/RouteRegistrarTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,17 @@ public function testCanRegisterPostRouteWithClosureAction()
$this->seeMiddleware('post-middleware');
}

public function testCanRegisterQueryRouteWithClosureAction()
{
$this->router->middleware('query-middleware')->query('users', function () {
return 'found';
});

$this->assertTrue($this->getRoute()->matches(Request::create('users', 'QUERY')));
$this->assertSame(['QUERY'], $this->getRoute()->methods());
$this->seeMiddleware('query-middleware');
}

public function testCanRegisterAnyRouteWithClosureAction()
{
$this->router->middleware('test-middleware')->any('users', function () {
Expand Down
5 changes: 5 additions & 0 deletions tests/Routing/RoutingRouteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,12 @@ public function testBasicDispatchingOfRoutes()
$router->post('foo/bar', function () {
return 'post hello';
});
$router->query('foo/bar', function () {
return 'query hello';
});
$this->assertSame('hello', $router->dispatch(Request::create('foo/bar', 'GET'))->getContent());
$this->assertSame('post hello', $router->dispatch(Request::create('foo/bar', 'POST'))->getContent());
$this->assertSame('query hello', $router->dispatch(Request::create('foo/bar', 'QUERY'))->getContent());

$router = $this->getRouter();
$router->get('foo/{bar}', function ($name) {
Expand Down Expand Up @@ -157,6 +161,7 @@ public function testBasicDispatchingOfRoutes()
return 'hello';
});
$this->assertEmpty($router->dispatch(Request::create('foo/bar', 'HEAD'))->getContent());
$this->assertSame('hello', $router->dispatch(Request::create('foo/bar', 'QUERY'))->getContent());

$router = $this->getRouter();
$router->get('foo/bar', function () {
Expand Down
Loading