Skip to content

Commit eff557c

Browse files
committed
Add query() route method for the HTTP QUERY verb (RFC 10008)
The HTTP QUERY method was recently published as RFC 10008 on the IETF Standards Track. It is a safe, cacheable method that carries its query in the request body, enabling expressive queries without the URL-length and cacheability trade-offs of GET vs. POST. Support for QUERY is landing across the ecosystem (https://www.rfc-editor.org/rfc/rfc10008), so this adds a query() helper as a sibling of the existing get()/post()/put()/patch()/ delete()/options() route methods on RouteCollectorProxyInter implemented via the existing map() mechanism: ``` $app->query('/search', function (Request $request, Response $response) { $criteria = (string) $request->getBody(); // ...run the query described in the request body... return $response; }); ``` `$app->query()` is equivalent to `$app->map(['QUERY'], ...)` and is not added to any(), so existing any() routes are unaffected.
1 parent 80900fb commit eff557c

5 files changed

Lines changed: 85 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
### Added
88

9+
- Add `query()` route helper for the HTTP `QUERY` method ([RFC 10008](https://www.rfc-editor.org/rfc/rfc10008))
10+
911
### Changed
1012

1113
### Removed

Slim/Interfaces/RouteCollectorProxyInterface.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ public function delete(string $pattern, $callable): RouteInterface;
9090
*/
9191
public function options(string $pattern, $callable): RouteInterface;
9292

93+
/**
94+
* Add QUERY route
95+
*
96+
* @param string $pattern The route URI pattern
97+
* @param callable|array{class-string, string}|string $callable The route callback routine
98+
*
99+
* @link https://www.rfc-editor.org/rfc/rfc10008
100+
*/
101+
public function query(string $pattern, $callable): RouteInterface;
102+
93103
/**
94104
* Add route for any HTTP method
95105
*

Slim/Routing/RouteCollectorProxy.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,14 @@ public function options(string $pattern, $callable): RouteInterface
151151
return $this->map(['OPTIONS'], $pattern, $callable);
152152
}
153153

154+
/**
155+
* {@inheritdoc}
156+
*/
157+
public function query(string $pattern, $callable): RouteInterface
158+
{
159+
return $this->map(['QUERY'], $pattern, $callable);
160+
}
161+
154162
/**
155163
* {@inheritdoc}
156164
*/

tests/AppTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,38 @@ public function testAnyRoute(): void
241241
}
242242
}
243243

244+
public function testQueryRoute(): void
245+
{
246+
$streamProphecy = $this->prophesize(StreamInterface::class);
247+
$streamProphecy->__toString()->willReturn('Hello World');
248+
249+
$responseProphecy = $this->prophesize(ResponseInterface::class);
250+
$responseProphecy->getBody()->willReturn($streamProphecy->reveal());
251+
252+
$responseFactoryProphecy = $this->prophesize(ResponseFactoryInterface::class);
253+
$responseFactoryProphecy->createResponse()->willReturn($responseProphecy->reveal());
254+
255+
$uriProphecy = $this->prophesize(UriInterface::class);
256+
$uriProphecy->getPath()->willReturn('/');
257+
258+
$requestProphecy = $this->prophesize(ServerRequestInterface::class);
259+
$requestProphecy->getMethod()->willReturn('QUERY');
260+
$requestProphecy->getUri()->willReturn($uriProphecy->reveal());
261+
$requestProphecy->getAttribute(RouteContext::ROUTING_RESULTS)->willReturn(null);
262+
$requestProphecy->withAttribute(Argument::type('string'), Argument::any())->will(function ($args) {
263+
$this->getAttribute($args[0])->willReturn($args[1]);
264+
return $this;
265+
});
266+
267+
$app = new App($responseFactoryProphecy->reveal());
268+
$app->query('/', function (ServerRequestInterface $request, ResponseInterface $response) {
269+
return $response;
270+
});
271+
$response = $app->handle($requestProphecy->reveal());
272+
273+
$this->assertSame('Hello World', (string) $response->getBody());
274+
}
275+
244276
/********************************************************************************
245277
* Route collector proxy methods
246278
*******************************************************************************/

tests/Routing/RouteCollectorProxyTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,39 @@ public function testOptions()
317317
$this->assertSame($pattern, $route->getPattern());
318318
}
319319

320+
public function testQuery()
321+
{
322+
$responseFactoryProphecy = $this->prophesize(ResponseFactoryInterface::class);
323+
$callableResolverProphecy = $this->prophesize(CallableResolverInterface::class);
324+
325+
$pattern = '/';
326+
$callable = function () {
327+
};
328+
329+
$routeProphecy = $this->prophesize(RouteInterface::class);
330+
$routeProphecy
331+
->getPattern()
332+
->willReturn($pattern)
333+
->shouldBeCalledOnce();
334+
335+
$routeCollectorProphecy = $this->prophesize(RouteCollectorInterface::class);
336+
$routeCollectorProphecy
337+
->map(['QUERY'], $pattern, Argument::is($callable))
338+
->willReturn($routeProphecy->reveal())
339+
->shouldBeCalledOnce();
340+
341+
$routeCollectorProxy = new RouteCollectorProxy(
342+
$responseFactoryProphecy->reveal(),
343+
$callableResolverProphecy->reveal(),
344+
null,
345+
$routeCollectorProphecy->reveal()
346+
);
347+
348+
$route = $routeCollectorProxy->query($pattern, $callable);
349+
350+
$this->assertSame($pattern, $route->getPattern());
351+
}
352+
320353
public function testAny()
321354
{
322355
$responseFactoryProphecy = $this->prophesize(ResponseFactoryInterface::class);

0 commit comments

Comments
 (0)