diff --git a/composer.json b/composer.json index 46fdef11218c0..56c224e216378 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php72": "~1.5", - "symfony/polyfill-php73": "^1.8" + "symfony/polyfill-php73": "^1.11" }, "replace": { "symfony/asset": "self.version", diff --git a/src/Symfony/Component/HttpClient/Exception/JsonException.php b/src/Symfony/Component/HttpClient/Exception/JsonException.php new file mode 100644 index 0000000000000..5dd63455803aa --- /dev/null +++ b/src/Symfony/Component/HttpClient/Exception/JsonException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * Thrown by responses' toArray() method when their content cannot be JSON-decoded. + * + * @author Nicolas Grekas + * + * @internal + */ +final class JsonException extends \JsonException implements TransportExceptionInterface +{ +} diff --git a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php index a83a4de516763..3d890b911d59b 100644 --- a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php +++ b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php @@ -15,6 +15,7 @@ use Symfony\Component\HttpClient\Chunk\ErrorChunk; use Symfony\Component\HttpClient\Chunk\LastChunk; use Symfony\Component\HttpClient\Exception\ClientException; +use Symfony\Component\HttpClient\Exception\JsonException; use Symfony\Component\HttpClient\Exception\RedirectionException; use Symfony\Component\HttpClient\Exception\ServerException; use Symfony\Component\HttpClient\Exception\TransportException; @@ -52,6 +53,7 @@ trait ResponseTrait private $timeout; private $finalInfo; private $offset = 0; + private $jsonData; /** * {@inheritdoc} @@ -121,6 +123,47 @@ public function getContent(bool $throw = true): string return stream_get_contents($this->content); } + /** + * {@inheritdoc} + */ + public function toArray(bool $throw = true): array + { + if ('' === $content = $this->getContent($throw)) { + throw new TransportException('Response body is empty.'); + } + + if (null !== $this->jsonData) { + return $this->jsonData; + } + + $contentType = $this->headers['content-type'][0] ?? 'application/json'; + + if (!preg_match('/\bjson\b/i', $contentType)) { + throw new JsonException(sprintf('Response content-type is "%s" while a JSON-compatible one was expected.', $contentType)); + } + + 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($e->getMessage(), $e->getCode()); + } + + if (\PHP_VERSION_ID < 70300 && JSON_ERROR_NONE !== json_last_error()) { + throw new JsonException(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.', \gettype($content))); + } + + if (null !== $this->content) { + // Option "buffer" is true + return $this->jsonData = $content; + } + + return $content; + } + /** * Closes the response and all its network handles. */ diff --git a/src/Symfony/Component/HttpClient/composer.json b/src/Symfony/Component/HttpClient/composer.json index 854c2c64fe9c2..979385626d2cb 100644 --- a/src/Symfony/Component/HttpClient/composer.json +++ b/src/Symfony/Component/HttpClient/composer.json @@ -20,7 +20,8 @@ }, "require": { "php": "^7.1.3", - "symfony/contracts": "^1.1" + "symfony/contracts": "^1.1", + "symfony/polyfill-php73": "^1.11" }, "require-dev": { "nyholm/psr7": "^1.0", diff --git a/src/Symfony/Contracts/HttpClient/ResponseInterface.php b/src/Symfony/Contracts/HttpClient/ResponseInterface.php index 244accc094acb..549bfcda3f18c 100644 --- a/src/Symfony/Contracts/HttpClient/ResponseInterface.php +++ b/src/Symfony/Contracts/HttpClient/ResponseInterface.php @@ -59,6 +59,18 @@ public function getHeaders(bool $throw = true): array; */ public function getContent(bool $throw = true): string; + /** + * Gets the response body decoded as array, typically from a JSON payload. + * + * @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes + * + * @throws TransportExceptionInterface When the body cannot be decoded or when a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function toArray(bool $throw = true): array; + /** * Returns info coming from the transport layer. * diff --git a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php index af6e0dc3759b0..fb63a9f07d9f5 100644 --- a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php +++ b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php @@ -58,6 +58,7 @@ public function testGetRequest() $this->assertSame(['application/json'], $headers['content-type']); $body = json_decode($response->getContent(), true); + $this->assertSame($body, $response->toArray()); $this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']); $this->assertSame('/', $body['REQUEST_URI']); @@ -79,7 +80,7 @@ public function testNonBufferedGetRequest() 'headers' => ['Foo' => 'baR'], ]); - $body = json_decode($response->getContent(), true); + $body = $response->toArray(); $this->assertSame('baR', $body['HTTP_FOO']); $this->expectException(TransportExceptionInterface::class); @@ -106,7 +107,7 @@ public function testHttpVersion() $this->assertSame(200, $response->getStatusCode()); $this->assertSame('HTTP/1.0 200 OK', $response->getInfo('raw_headers')[0]); - $body = json_decode($response->getContent(), true); + $body = $response->toArray(); $this->assertSame('HTTP/1.0', $body['SERVER_PROTOCOL']); $this->assertSame('GET', $body['REQUEST_METHOD']); @@ -203,7 +204,7 @@ public function testInlineAuth() $client = $this->getHttpClient(); $response = $client->request('GET', 'http://foo:bar%3Dbar@localhost:8057'); - $body = json_decode($response->getContent(), true); + $body = $response->toArray(); $this->assertSame('foo', $body['PHP_AUTH_USER']); $this->assertSame('bar=bar', $body['PHP_AUTH_PW']); @@ -219,7 +220,7 @@ public function testRedirects() }, ]); - $body = json_decode($response->getContent(), true); + $body = $response->toArray(); $this->assertSame('GET', $body['REQUEST_METHOD']); $this->assertSame('Basic Zm9vOmJhcg==', $body['HTTP_AUTHORIZATION']); $this->assertSame('http://localhost:8057/', $response->getInfo('url')); @@ -250,7 +251,8 @@ public function testRelativeRedirects() $client = $this->getHttpClient(); $response = $client->request('GET', 'http://localhost:8057/302/relative'); - $body = json_decode($response->getContent(), true); + $body = $response->toArray(); + $this->assertSame('/', $body['REQUEST_URI']); $this->assertNull($response->getInfo('redirect_url')); @@ -279,7 +281,7 @@ public function testRedirect307() 'body' => 'foo=bar', ]); - $body = json_decode($response->getContent(), true); + $body = $response->toArray(); $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $body); } @@ -388,7 +390,7 @@ public function testOnProgress() 'on_progress' => function (...$state) use (&$steps) { $steps[] = $state; }, ]); - $body = json_decode($response->getContent(), true); + $body = $response->toArray(); $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body); $this->assertSame([0, 0], \array_slice($steps[0], 0, 2)); @@ -405,7 +407,7 @@ public function testPostArray() 'body' => ['foo' => 'bar'], ]); - $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], json_decode($response->getContent(), true)); + $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $response->toArray()); } public function testPostResource() @@ -420,7 +422,7 @@ public function testPostResource() 'body' => $h, ]); - $body = json_decode($response->getContent(), true); + $body = $response->toArray(); $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body); } @@ -438,7 +440,7 @@ public function testPostCallback() }, ]); - $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], json_decode($response->getContent(), true)); + $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $response->toArray()); } public function testOnProgressCancel() @@ -581,7 +583,7 @@ public function testProxy() 'proxy' => 'http://localhost:8057', ]); - $body = json_decode($response->getContent(), true); + $body = $response->toArray(); $this->assertSame('localhost:8057', $body['HTTP_HOST']); $this->assertRegexp('#^http://(localhost|127\.0\.0\.1):8057/$#', $body['REQUEST_URI']); @@ -589,7 +591,7 @@ public function testProxy() 'proxy' => 'http://foo:b%3Dar@localhost:8057', ]); - $body = json_decode($response->getContent(), true); + $body = $response->toArray(); $this->assertSame('Basic Zm9vOmI9YXI=', $body['HTTP_PROXY_AUTHORIZATION']); } @@ -603,7 +605,7 @@ public function testNoProxy() 'proxy' => 'http://localhost:8057', ]); - $body = json_decode($response->getContent(), true); + $body = $response->toArray(); $this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']); $this->assertSame('/', $body['REQUEST_URI']); @@ -629,7 +631,7 @@ public function testAutoEncodingRequest() $this->assertSame(['Accept-Encoding'], $headers['vary']); $this->assertContains('gzip', $headers['content-encoding'][0]); - $body = json_decode($response->getContent(), true); + $body = $response->toArray(); $this->assertContains('gzip', $body['HTTP_ACCEPT_ENCODING']); } @@ -652,7 +654,7 @@ public function testQuery() 'query' => ['b' => 'b'], ]); - $body = json_decode($response->getContent(), true); + $body = $response->toArray(); $this->assertSame('GET', $body['REQUEST_METHOD']); $this->assertSame('/?a=a&b=b', $body['REQUEST_URI']); } @@ -673,10 +675,9 @@ public function testUserlandEncodingRequest() $this->assertContains('gzip', $headers['content-encoding'][0]); $body = $response->getContent(); - $this->assertSame("\x1F", $body[0]); - $body = json_decode(gzdecode($body), true); + $body = json_decode(gzdecode($body), true); $this->assertSame('gzip', $body['HTTP_ACCEPT_ENCODING']); } 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