diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 277efc4fae28..17d380456670 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * added `HeaderUtils::parseQuery()`: it does the same as `parse_str()` but preserves dots in variable names * added `File::getContent()` * added ability to use comma separated ip addresses for `RequestMatcher::matchIps()` + * added `Request::toArray()` to parse a JSON request body to an array * added `RateLimiter\RequestRateLimiterInterface` and `RateLimiter\AbstractRequestRateLimiter` 5.1.0 diff --git a/src/Symfony/Component/HttpFoundation/Exception/JsonException.php b/src/Symfony/Component/HttpFoundation/Exception/JsonException.php new file mode 100644 index 000000000000..5990e760e6d2 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Exception/JsonException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Thrown by Request::toArray() when the content cannot be JSON-decoded. + * + * @author Tobias Nyholm + */ +final class JsonException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 7c59428b7546..286c329614ed 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation; use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Exception\JsonException; use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; use Symfony\Component\HttpFoundation\Session\SessionInterface; @@ -1569,6 +1570,34 @@ public function getContent(bool $asResource = false) return $this->content; } + /** + * Gets the request body decoded as array, typically from a JSON payload. + * + * @throws JsonException When the body cannot be decoded to an array + */ + public function toArray(): array + { + if ('' === $content = $this->getContent()) { + throw new JsonException('Response body is empty.'); + } + + try { + $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0)); + } catch (\JsonException $e) { + throw new JsonException('Could not decode request body.', $e->getCode(), $e); + } + + if (\PHP_VERSION_ID < 70300 && \JSON_ERROR_NONE !== json_last_error()) { + throw new JsonException('Could not decode request body: '.json_last_error_msg(), json_last_error()); + } + + if (!\is_array($content)) { + throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content))); + } + + return $content; + } + /** * Gets the Etags. * diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 358d2b140adf..5d466afe25f8 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Exception\JsonException; use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; use Symfony\Component\HttpFoundation\InputBag; use Symfony\Component\HttpFoundation\ParameterBag; @@ -1250,6 +1251,30 @@ public function provideOverloadedMethods() ]; } + public function testToArrayEmpty() + { + $req = new Request(); + $this->expectException(JsonException::class); + $this->expectExceptionMessage('Response body is empty.'); + $req->toArray(); + } + + public function testToArrayNonJson() + { + $req = new Request([], [], [], [], [], [], 'foobar'); + $this->expectException(JsonException::class); + $this->expectExceptionMessageMatches('|Could not decode request body.+|'); + $req->toArray(); + } + + public function testToArray() + { + $req = new Request([], [], [], [], [], [], json_encode([])); + $this->assertEquals([], $req->toArray()); + $req = new Request([], [], [], [], [], [], json_encode(['foo' => 'bar'])); + $this->assertEquals(['foo' => 'bar'], $req->toArray()); + } + /** * @dataProvider provideOverloadedMethods */