diff --git a/src/Http/Batch.php b/src/Http/Batch.php index d16708f20..1ad1c5931 100644 --- a/src/Http/Batch.php +++ b/src/Http/Batch.php @@ -164,6 +164,13 @@ public function parseResponse(ResponseInterface $response, $classes = []) $responses = []; $requests = array_values($this->requests); + // Key the requests the same way the API keys the response parts, so + // a part can be matched to the request which produced it. + $requestsByContentId = []; + foreach ($this->requests as $requestKey => $batchedRequest) { + $requestsByContentId['response-' . $requestKey] = $batchedRequest; + } + foreach ($parts as $i => $part) { $part = trim($part); if (!empty($part)) { @@ -184,8 +191,18 @@ public function parseResponse(ResponseInterface $response, $classes = []) // Need content id. $key = $headers['content-id']; + // The parts are not guaranteed to be returned in the order + // they were sent, so resolve the request and the expected + // class from the content id, and only fall back to the + // position of the part when the content id is unknown. + $request = $requestsByContentId[$key] ?? ($requests[$i-1] ?? null); + try { - $response = REST::decodeHttpResponse($response, $requests[$i-1]); + $response = REST::decodeHttpResponse( + $response, + $request, + $classes[$key] ?? null + ); } catch (GoogleServiceException $e) { // Store the exception as the response, so successful responses // can be processed. diff --git a/tests/Google/Http/BatchTest.php b/tests/Google/Http/BatchTest.php index e60421a06..0647c30ac 100644 --- a/tests/Google/Http/BatchTest.php +++ b/tests/Google/Http/BatchTest.php @@ -26,6 +26,8 @@ use Google\Service\Storage; use Google\Service\Exception as ServiceException; use GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; class BatchTest extends BaseTest { @@ -65,6 +67,57 @@ public function testInvalidBatchRequest() ); } + public function testParseResponseMatchesResponsesToRequestsByContentId() + { + // The API is not required to return the parts in the order they were + // sent, so each part must be matched to its request via its Content-ID. + $client = $this->getClient(); + $batch = new Batch($client); + $batch->add( + (new Request('GET', 'http://foo.bar/volume')) + ->withHeader('X-Php-Expected-Class', Books\Volume::class), + 'key1' + ); + $batch->add( + (new Request('GET', 'http://foo.bar/bookshelf')) + ->withHeader('X-Php-Expected-Class', Books\Bookshelf::class), + 'key2' + ); + + $boundary = 'batch_boundary'; + $classes = [ + 'response-key1' => Books\Volume::class, + 'response-key2' => Books\Bookshelf::class, + ]; + + // The parts come back in the reverse order of the requests. + $body = ''; + foreach (['key2', 'key1'] as $key) { + $body .= "--$boundary\r\n" + . "Content-Type: application/http\r\n" + . "Content-ID: response-$key\r\n" + . "\r\n" + . "HTTP/1.1 200 OK\r\n" + . "Content-Type: application/json\r\n" + . "\r\n" + . '{"id": "' . $key . '"}' . "\r\n"; + } + $body .= "--$boundary--"; + + $response = new Response( + 200, + ['Content-Type' => "multipart/mixed; boundary=$boundary"], + $body + ); + + $result = $batch->parseResponse($response, $classes); + + $this->assertInstanceOf(Books\Volume::class, $result['response-key1']); + $this->assertEquals('key1', $result['response-key1']->id); + $this->assertInstanceOf(Books\Bookshelf::class, $result['response-key2']); + $this->assertEquals('key2', $result['response-key2']->id); + } + public function testMediaFileBatch() { $client = $this->getClient();