Skip to content

Commit 0cfd34f

Browse files
[HttpFoundation][HttpKernel] Add support for the QUERY HTTP method
1 parent b065b9a commit 0cfd34f

File tree

7 files changed

+170
-4
lines changed

7 files changed

+170
-4
lines changed

src/Symfony/Component/HttpFoundation/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Deprecate using `Request::sendHeaders()` after headers have already been sent; use a `StreamedResponse` instead
8+
* Add support for the `QUERY` HTTP method
89

910
7.3
1011
---

src/Symfony/Component/HttpFoundation/Request.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class Request
6262
public const METHOD_OPTIONS = 'OPTIONS';
6363
public const METHOD_TRACE = 'TRACE';
6464
public const METHOD_CONNECT = 'CONNECT';
65+
public const METHOD_QUERY = 'QUERY';
6566

6667
/**
6768
* @var string[]
@@ -1351,15 +1352,15 @@ public function isMethod(string $method): bool
13511352
*/
13521353
public function isMethodSafe(): bool
13531354
{
1354-
return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE'], true);
1355+
return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE', 'QUERY'], true);
13551356
}
13561357

13571358
/**
13581359
* Checks whether or not the method is idempotent.
13591360
*/
13601361
public function isMethodIdempotent(): bool
13611362
{
1362-
return \in_array($this->getMethod(), ['HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE'], true);
1363+
return \in_array($this->getMethod(), ['HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE', 'QUERY'], true);
13631364
}
13641365

13651366
/**
@@ -1369,7 +1370,7 @@ public function isMethodIdempotent(): bool
13691370
*/
13701371
public function isMethodCacheable(): bool
13711372
{
1372-
return \in_array($this->getMethod(), ['GET', 'HEAD'], true);
1373+
return \in_array($this->getMethod(), ['GET', 'HEAD', 'QUERY'], true);
13731374
}
13741375

13751376
/**

src/Symfony/Component/HttpFoundation/Tests/RequestTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2330,6 +2330,7 @@ public static function methodIdempotentProvider()
23302330
['OPTIONS', true],
23312331
['TRACE', true],
23322332
['CONNECT', false],
2333+
['QUERY', true],
23332334
];
23342335
}
23352336

@@ -2356,6 +2357,7 @@ public static function methodSafeProvider()
23562357
['OPTIONS', true],
23572358
['TRACE', true],
23582359
['CONNECT', false],
2360+
['QUERY', true],
23592361
];
23602362
}
23612363

@@ -2382,6 +2384,7 @@ public static function methodCacheableProvider()
23822384
['OPTIONS', false],
23832385
['TRACE', false],
23842386
['CONNECT', false],
2387+
['QUERY', true],
23852388
];
23862389
}
23872390

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.4
5+
---
6+
7+
* Add support for the `QUERY` HTTP method
8+
49
7.3
510
---
611

src/Symfony/Component/HttpKernel/HttpCache/Store.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,13 @@ public function getPath(string $key): string
427427
*/
428428
protected function generateCacheKey(Request $request): string
429429
{
430-
return 'md'.hash('sha256', $request->getUri());
430+
$key = $request->getUri();
431+
432+
if ('QUERY' === $request->getMethod()) {
433+
$key .= $request->getContent();
434+
}
435+
436+
return 'md'.hash('sha256', $key);
431437
}
432438

433439
/**

src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2070,6 +2070,92 @@ public function testTraceLevelShort()
20702070
$this->assertTrue($this->response->headers->has('X-Symfony-Cache'));
20712071
$this->assertEquals('miss', $this->response->headers->get('X-Symfony-Cache'));
20722072
}
2073+
2074+
public function testQueryMethodIsCacheable()
2075+
{
2076+
$this->setNextResponse(200, ['Cache-Control' => 'public, max-age=10000'], 'Query result', function (Request $request) {
2077+
$this->assertSame('QUERY', $request->getMethod());
2078+
return '{"query": "users"}' === $request->getContent();
2079+
});
2080+
2081+
$this->kernel->reset();
2082+
$this->store = $this->createStore();
2083+
$this->cacheConfig['debug'] = true;
2084+
$this->cache = new HttpCache($this->kernel, $this->store, null, $this->cacheConfig);
2085+
2086+
$request1 = Request::create('/', 'QUERY', [], [], [], [], '{"query": "users"}');
2087+
$this->response = $this->cache->handle($request1, HttpKernelInterface::MAIN_REQUEST, $this->catch);
2088+
2089+
$this->assertSame(200, $this->response->getStatusCode());
2090+
$this->assertTraceContains('miss');
2091+
$this->assertSame('Query result', $this->response->getContent());
2092+
2093+
$request2 = Request::create('/', 'QUERY', [], [], [], [], '{"query": "users"}');
2094+
$this->response = $this->cache->handle($request2, HttpKernelInterface::MAIN_REQUEST, $this->catch);
2095+
2096+
$this->assertSame(200, $this->response->getStatusCode());
2097+
$this->assertTrue($this->response->headers->has('Age'));
2098+
$this->assertSame('Query result', $this->response->getContent());
2099+
}
2100+
2101+
public function testQueryMethodDifferentBodiesCreateDifferentCacheEntries()
2102+
{
2103+
$this->setNextResponses([
2104+
[
2105+
'status' => 200,
2106+
'body' => 'Users result',
2107+
'headers' => ['Cache-Control' => 'public, max-age=10000'],
2108+
],
2109+
[
2110+
'status' => 200,
2111+
'body' => 'Posts result',
2112+
'headers' => ['Cache-Control' => 'public, max-age=10000'],
2113+
],
2114+
]);
2115+
2116+
$this->store = $this->createStore();
2117+
$this->cacheConfig['debug'] = true;
2118+
$this->cache = new HttpCache($this->kernel, $this->store, null, $this->cacheConfig);
2119+
2120+
$request1 = Request::create('/', 'QUERY', [], [], [], [], '{"query": "users"}');
2121+
$this->response = $this->cache->handle($request1, HttpKernelInterface::MAIN_REQUEST, $this->catch);
2122+
2123+
$this->assertSame('Users result', $this->response->getContent());
2124+
$this->assertTraceContains('miss');
2125+
2126+
$request2 = Request::create('/', 'QUERY', [], [], [], [], '{"query": "posts"}');
2127+
$this->response = $this->cache->handle($request2, HttpKernelInterface::MAIN_REQUEST, $this->catch);
2128+
2129+
$this->assertSame('Posts result', $this->response->getContent());
2130+
$this->assertTraceContains('miss');
2131+
2132+
$request3 = Request::create('/', 'QUERY', [], [], [], [], '{"query": "users"}');
2133+
$this->response = $this->cache->handle($request3, HttpKernelInterface::MAIN_REQUEST, $this->catch);
2134+
2135+
$this->assertSame('Users result', $this->response->getContent());
2136+
$this->assertTrue($this->response->headers->has('Age'));
2137+
}
2138+
2139+
public function testQueryMethodWithEmptyBodyIsCacheable()
2140+
{
2141+
$this->setNextResponse(200, ['Cache-Control' => 'public, max-age=10000'], 'Empty query result');
2142+
$this->kernel->reset();
2143+
$this->store = $this->createStore();
2144+
$this->cacheConfig['debug'] = true;
2145+
$this->cache = new HttpCache($this->kernel, $this->store, null, $this->cacheConfig);
2146+
2147+
$request1 = Request::create('/', 'QUERY', [], [], [], [], '');
2148+
$this->response = $this->cache->handle($request1, HttpKernelInterface::MAIN_REQUEST, $this->catch);
2149+
2150+
$this->assertSame(200, $this->response->getStatusCode());
2151+
$this->assertTraceContains('miss');
2152+
2153+
$request2 = Request::create('/', 'QUERY', [], [], [], [], '');
2154+
$this->response = $this->cache->handle($request2, HttpKernelInterface::MAIN_REQUEST, $this->catch);
2155+
2156+
$this->assertSame(200, $this->response->getStatusCode());
2157+
$this->assertTrue($this->response->headers->has('Age'));
2158+
}
20732159
}
20742160

20752161
class TestKernel implements HttpKernelInterface

src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,4 +378,68 @@ protected function getStorePath($key)
378378

379379
return $m->invoke($this->store, $key);
380380
}
381+
382+
public function testQueryMethodCacheKeyIncludesBody()
383+
{
384+
$response = new Response('test', 200, ['Cache-Control' => 'max-age=420']);
385+
386+
$request1 = Request::create('/', 'QUERY', [], [], [], [], '{"query": "users"}');
387+
$request2 = Request::create('/', 'QUERY', [], [], [], [], '{"query": "posts"}');
388+
$request3 = Request::create('/', 'QUERY', [], [], [], [], '{"query": "users"}');
389+
390+
$key1 = $this->store->write($request1, $response);
391+
$key2 = $this->store->write($request2, $response);
392+
$key3 = $this->store->write($request3, $response);
393+
394+
$this->assertNotSame($key1, $key2);
395+
$this->assertSame($key1, $key3);
396+
397+
$this->assertNotEmpty($this->getStoreMetadata($key1));
398+
$this->assertNotEmpty($this->getStoreMetadata($key2));
399+
400+
$this->assertNotNull($this->store->lookup($request1));
401+
$this->assertNotNull($this->store->lookup($request2));
402+
$this->assertNotNull($this->store->lookup($request3));
403+
}
404+
405+
public function testQueryMethodCacheKeyDiffersFromGet()
406+
{
407+
$response = new Response('test', 200, ['Cache-Control' => 'max-age=420']);
408+
409+
$getRequest = Request::create('/');
410+
$queryRequest = Request::create('/', 'QUERY', [], [], [], [], '{"query": "test"}');
411+
412+
$getKey = $this->store->write($getRequest, $response);
413+
$queryKey = $this->store->write($queryRequest, $response);
414+
415+
$this->assertNotSame($getKey, $queryKey);
416+
417+
$this->assertNotEmpty($this->getStoreMetadata($getKey));
418+
$this->assertNotEmpty($this->getStoreMetadata($queryKey));
419+
420+
$this->assertNotNull($this->store->lookup($getRequest));
421+
$this->assertNotNull($this->store->lookup($queryRequest));
422+
}
423+
424+
public function testOtherMethodsCacheKeyIgnoresBody()
425+
{
426+
$response1 = new Response('test 1', 200, ['Cache-Control' => 'max-age=420']);
427+
$response2 = new Response('test 2', 200, ['Cache-Control' => 'max-age=420']);
428+
429+
$getRequest1 = Request::create('/', 'GET', [], [], [], [], '{"data": "test"}');
430+
$getRequest2 = Request::create('/', 'GET', [], [], [], [], '{"data": "different"}');
431+
432+
$key1 = $this->store->write($getRequest1, $response1);
433+
$key2 = $this->store->write($getRequest2, $response2);
434+
435+
$this->assertSame($key1, $key2);
436+
437+
$lookup1 = $this->store->lookup($getRequest1);
438+
$lookup2 = $this->store->lookup($getRequest2);
439+
$this->assertNotNull($lookup1);
440+
$this->assertNotNull($lookup2);
441+
442+
$this->assertCount(1, $this->getStoreMetadata($key1));
443+
$this->assertSame($lookup1->getContent(), $lookup2->getContent());
444+
}
381445
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy