Skip to content

Commit 6363c41

Browse files
committed
Request: add getBody (interface BC break)
1 parent 4fb8860 commit 6363c41

File tree

5 files changed

+156
-4
lines changed

5 files changed

+156
-4
lines changed

src/Http/IRequest.php

+6
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,10 @@ function getRemoteHost();
135135
*/
136136
function getRawBody();
137137

138+
/**
139+
* Returns parsed content of HTTP request body.
140+
* @return mixed
141+
* @throws InvalidRequestBodyException
142+
*/
143+
function getBody();
138144
}

src/Http/Request.php

+21-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
* @property-read string|NULL $remoteAddress
2727
* @property-read string|NULL $remoteHost
2828
* @property-read string|NULL $rawBody
29+
* @property-read string|NULL $body
2930
*/
3031
class Request implements IRequest
3132
{
@@ -58,9 +59,14 @@ class Request implements IRequest
5859
/** @var callable|NULL */
5960
private $rawBodyCallback;
6061

62+
/** @var callable|NULL */
63+
private $bodyCallback;
64+
6165

62-
public function __construct(UrlScript $url, $query = NULL, $post = NULL, $files = NULL, $cookies = NULL,
63-
$headers = NULL, $method = NULL, $remoteAddress = NULL, $remoteHost = NULL, $rawBodyCallback = NULL)
66+
public function __construct(
67+
UrlScript $url, $query = NULL, $post = NULL, $files = NULL, $cookies = NULL,
68+
$headers = NULL, $method = NULL, $remoteAddress = NULL, $remoteHost = NULL,
69+
$rawBodyCallback = NULL, $bodyCallback = NULL)
6470
{
6571
$this->url = $url;
6672
if ($query !== NULL) {
@@ -75,6 +81,7 @@ public function __construct(UrlScript $url, $query = NULL, $post = NULL, $files
7581
$this->remoteAddress = $remoteAddress;
7682
$this->remoteHost = $remoteHost;
7783
$this->rawBodyCallback = $rawBodyCallback;
84+
$this->bodyCallback = $bodyCallback;
7885
}
7986

8087

@@ -289,7 +296,18 @@ public function getRemoteHost()
289296
*/
290297
public function getRawBody()
291298
{
292-
return $this->rawBodyCallback ? call_user_func($this->rawBodyCallback) : NULL;
299+
return $this->rawBodyCallback ? call_user_func($this->rawBodyCallback, $this) : NULL;
300+
}
301+
302+
303+
/**
304+
* Returns parsed content of HTTP request body.
305+
* @return mixed
306+
* @throws InvalidRequestBodyException
307+
*/
308+
public function getBody()
309+
{
310+
return $this->bodyCallback ? call_user_func($this->bodyCallback, $this) : NULL;
293311
}
294312

295313

src/Http/RequestFactory.php

+48-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Nette\Http;
99

1010
use Nette;
11+
use Nette\Utils\Json;
1112
use Nette\Utils\Strings;
1213

1314

@@ -33,6 +34,25 @@ class RequestFactory
3334
/** @var array */
3435
private $proxies = [];
3536

37+
/** @var callable[] of function (Request $request): mixed */
38+
private $bodyParsers = [];
39+
40+
41+
public function __construct()
42+
{
43+
$this->addBodyParser('application/x-www-form-urlencoded', function (Request $request) {
44+
return $request->getPost();
45+
});
46+
47+
$this->addBodyParser('application/json', function (Request $request) {
48+
try {
49+
return Json::decode($request->getRawBody());
50+
} catch (Nette\Utils\JsonException $e) {
51+
throw new InvalidRequestBodyException('Body is not a valid JSON', 0, $e);
52+
}
53+
});
54+
}
55+
3656

3757
/**
3858
* @param bool
@@ -56,6 +76,18 @@ public function setProxy($proxy)
5676
}
5777

5878

79+
/**
80+
* @param string
81+
* @param callable function(Request $request): mixed|NULL
82+
* @return self
83+
*/
84+
public function addBodyParser($contentType, $callback)
85+
{
86+
$this->bodyParsers[$contentType] = $callback;
87+
return $this;
88+
}
89+
90+
5991
/**
6092
* Creates current HttpRequest object.
6193
* @return Request
@@ -235,7 +267,22 @@ public function createHttpRequest()
235267
return file_get_contents('php://input');
236268
};
237269

238-
return new Request($url, NULL, $post, $files, $cookies, $headers, $method, $remoteAddr, $remoteHost, $rawBodyCallback);
270+
// parsed body
271+
$bodyCallback = function (Request $request) use (& $body) {
272+
if ($body === NULL) {
273+
$contentType = $request->getHeader('Content-Type');
274+
foreach ($this->bodyParsers as $parserContentType => $parser) {
275+
if (stripos($contentType, $parserContentType) === 0) {
276+
$body = $parser($request);
277+
break;
278+
}
279+
}
280+
}
281+
282+
return $body;
283+
};
284+
285+
return new Request($url, NULL, $post, $files, $cookies, $headers, $method, $remoteAddr, $remoteHost, $rawBodyCallback, $bodyCallback);
239286
}
240287

241288
}

src/Http/exceptions.php

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Framework (https://nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
namespace Nette\Http;
9+
10+
use Nette;
11+
12+
13+
/**
14+
* Exception is thrown when a request body can not be parsed.
15+
*/
16+
class InvalidRequestBodyException extends \RuntimeException
17+
{
18+
}

tests/Http/RequestFactory.body.phpt

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Http\RequestFactory body parsing.
5+
*/
6+
7+
use Nette\Http\InvalidRequestBodyException;
8+
use Nette\Http\RequestFactory;
9+
use Nette\Utils\JsonException;
10+
use Tester\Assert;
11+
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
15+
16+
$factory = new RequestFactory;
17+
18+
$setRawBody = function ($rawBody) {
19+
$this->rawBodyCallback = function () use ($rawBody) {
20+
return $rawBody;
21+
};
22+
};
23+
24+
25+
test(function () use ($factory, $setRawBody) {
26+
$_SERVER = [
27+
'CONTENT_TYPE' => 'application/json',
28+
];
29+
30+
$request = $factory->createHttpRequest();
31+
$setRawBody->bindTo($request, $request)->__invoke('[1, 2.0, "3", true, false, null, {}]');
32+
Assert::same('[1, 2.0, "3", true, false, null, {}]', $request->getRawBody());
33+
Assert::equal([1, 2.0, '3', TRUE, FALSE, NULL, new stdClass], $request->body);
34+
});
35+
36+
37+
test(function () use ($factory, $setRawBody) {
38+
$_SERVER = [
39+
'CONTENT_TYPE' => 'application/json',
40+
];
41+
42+
$request = $factory->createHttpRequest();
43+
$setRawBody->bindTo($request, $request)->__invoke('[');
44+
Assert::same('[', $request->getRawBody());
45+
$e = Assert::exception([$request, 'getBody'], InvalidRequestBodyException::class);
46+
Assert::type(JsonException::class, $e->getPrevious());
47+
});
48+
49+
50+
test(function () use ($factory, $setRawBody) {
51+
$_SERVER = [
52+
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
53+
];
54+
55+
$_POST = [
56+
'a' => 'b',
57+
];
58+
59+
$request = $factory->createHttpRequest();
60+
$setRawBody->bindTo($request, $request)->__invoke('a=c');
61+
Assert::same('a=c', $request->getRawBody());
62+
Assert::equal(['a' => 'b'], $request->body);
63+
});

0 commit comments

Comments
 (0)