Google_Http_Batch   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 221
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 121
c 1
b 0
f 1
dl 0
loc 221
rs 10
wmc 28

6 Methods

Rating   Name   Duplication   Size   Complexity  
B parseResponse() 0 55 8
A parseHttpResponse() 0 31 5
A execute() 0 63 4
A add() 0 7 2
A parseRawHeaders() 0 16 5
A __construct() 0 10 4
1
<?php
2
/*
3
 * Copyright 2012 Google Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *     http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
use GuzzleHttp\Psr7;
19
use GuzzleHttp\Psr7\Request;
20
use GuzzleHttp\Psr7\Response;
21
use Psr\Http\Message\RequestInterface;
22
use Psr\Http\Message\ResponseInterface;
23
24
/**
25
 * Class to handle batched requests to the Google API service.
26
 *
27
 * Note that calls to `Google_Http_Batch::execute()` do not clear the queued
28
 * requests. To start a new batch, be sure to create a new instance of this
29
 * class.
30
 */
31
class Google_Http_Batch
32
{
33
  const BATCH_PATH = 'batch';
34
35
  private static $CONNECTION_ESTABLISHED_HEADERS = array(
36
    "HTTP/1.0 200 Connection established\r\n\r\n",
37
    "HTTP/1.1 200 Connection established\r\n\r\n",
38
  );
39
40
  /** @var string Multipart Boundary. */
41
  private $boundary;
42
43
  /** @var array service requests to be executed. */
44
  private $requests = array();
45
46
  /** @var Google_Client */
47
  private $client;
48
49
  private $rootUrl;
50
51
  private $batchPath;
52
53
  public function __construct(
54
      Google_Client $client,
55
      $boundary = false,
56
      $rootUrl = null,
57
      $batchPath = null
58
  ) {
59
    $this->client = $client;
60
    $this->boundary = $boundary ?: mt_rand();
61
    $this->rootUrl = rtrim($rootUrl ?: $this->client->getConfig('base_path'), '/');
62
    $this->batchPath = $batchPath ?: self::BATCH_PATH;
63
  }
64
65
  public function add(RequestInterface $request, $key = false)
66
  {
67
    if (false == $key) {
68
      $key = mt_rand();
69
    }
70
71
    $this->requests[$key] = $request;
72
  }
73
74
  public function execute()
75
  {
76
    $body = '';
77
    $classes = array();
78
    $batchHttpTemplate = <<<EOF
79
--%s
80
Content-Type: application/http
81
Content-Transfer-Encoding: binary
82
MIME-Version: 1.0
83
Content-ID: %s
84
85
%s
86
%s%s
87
88
89
EOF;
90
91
    /** @var Google_Http_Request $req */
92
    foreach ($this->requests as $key => $request) {
93
      $firstLine = sprintf(
94
          '%s %s HTTP/%s',
95
          $request->getMethod(),
96
          $request->getRequestTarget(),
97
          $request->getProtocolVersion()
98
      );
99
100
      $content = (string) $request->getBody();
101
102
      $headers = '';
103
      foreach ($request->getHeaders() as $name => $values) {
104
          $headers .= sprintf("%s:%s\r\n", $name, implode(', ', $values));
105
      }
106
107
      $body .= sprintf(
108
          $batchHttpTemplate,
109
          $this->boundary,
110
          $key,
111
          $firstLine,
112
          $headers,
113
          $content ? "\n".$content : ''
114
      );
115
116
      $classes['response-' . $key] = $request->getHeaderLine('X-Php-Expected-Class');
117
    }
118
119
    $body .= "--{$this->boundary}--";
120
    $body = trim($body);
121
    $url = $this->rootUrl . '/' . $this->batchPath;
122
    $headers = array(
123
      'Content-Type' => sprintf('multipart/mixed; boundary=%s', $this->boundary),
124
      'Content-Length' => strlen($body),
125
    );
126
127
    $request = new Request(
128
        'POST',
129
        $url,
130
        $headers,
131
        $body
132
    );
133
134
    $response = $this->client->execute($request);
135
136
    return $this->parseResponse($response, $classes);
137
  }
138
139
  public function parseResponse(ResponseInterface $response, $classes = array())
0 ignored issues
show
Unused Code introduced by
The parameter $classes is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

139
  public function parseResponse(ResponseInterface $response, /** @scrutinizer ignore-unused */ $classes = array())

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
140
  {
141
    $contentType = $response->getHeaderLine('content-type');
142
    $contentType = explode(';', $contentType);
143
    $boundary = false;
144
    foreach ($contentType as $part) {
145
      $part = explode('=', $part, 2);
146
      if (isset($part[0]) && 'boundary' == trim($part[0])) {
147
        $boundary = $part[1];
148
      }
149
    }
150
151
    $body = (string) $response->getBody();
152
    if (!empty($body)) {
153
      $body = str_replace("--$boundary--", "--$boundary", $body);
154
      $parts = explode("--$boundary", $body);
155
      $responses = array();
156
      $requests = array_values($this->requests);
157
158
      foreach ($parts as $i => $part) {
159
        $part = trim($part);
160
        if (!empty($part)) {
161
          list($rawHeaders, $part) = explode("\r\n\r\n", $part, 2);
162
          $headers = $this->parseRawHeaders($rawHeaders);
163
164
          $status = substr($part, 0, strpos($part, "\n"));
165
          $status = explode(" ", $status);
166
          $status = $status[1];
167
168
          list($partHeaders, $partBody) = $this->parseHttpResponse($part, false);
169
          $response = new Response(
170
              $status,
0 ignored issues
show
Bug introduced by
$status of type string is incompatible with the type integer expected by parameter $status of GuzzleHttp\Psr7\Response::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

170
              /** @scrutinizer ignore-type */ $status,
Loading history...
171
              $partHeaders,
172
              Psr7\stream_for($partBody)
0 ignored issues
show
Bug introduced by
The function stream_for was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

172
              /** @scrutinizer ignore-call */ 
173
              Psr7\stream_for($partBody)
Loading history...
173
          );
174
175
          // Need content id.
176
          $key = $headers['content-id'];
177
178
          try {
179
            $response = Google_Http_REST::decodeHttpResponse($response, $requests[$i-1]);
180
          } catch (Google_Service_Exception $e) {
181
            // Store the exception as the response, so successful responses
182
            // can be processed.
183
            $response = $e;
184
          }
185
186
          $responses[$key] = $response;
187
        }
188
      }
189
190
      return $responses;
191
    }
192
193
    return null;
194
  }
195
196
  private function parseRawHeaders($rawHeaders)
197
  {
198
    $headers = array();
199
    $responseHeaderLines = explode("\r\n", $rawHeaders);
200
    foreach ($responseHeaderLines as $headerLine) {
201
      if ($headerLine && strpos($headerLine, ':') !== false) {
202
        list($header, $value) = explode(': ', $headerLine, 2);
203
        $header = strtolower($header);
204
        if (isset($headers[$header])) {
205
          $headers[$header] .= "\n" . $value;
206
        } else {
207
          $headers[$header] = $value;
208
        }
209
      }
210
    }
211
    return $headers;
212
  }
213
214
  /**
215
   * Used by the IO lib and also the batch processing.
216
   *
217
   * @param $respData
218
   * @param $headerSize
219
   * @return array
220
   */
221
  private function parseHttpResponse($respData, $headerSize)
222
  {
223
    // check proxy header
224
    foreach (self::$CONNECTION_ESTABLISHED_HEADERS as $established_header) {
225
      if (stripos($respData, $established_header) !== false) {
226
        // existed, remove it
227
        $respData = str_ireplace($established_header, '', $respData);
228
        // Subtract the proxy header size unless the cURL bug prior to 7.30.0
229
        // is present which prevented the proxy header size from being taken into
230
        // account.
231
        // @TODO look into this
232
        // if (!$this->needsQuirk()) {
233
        //   $headerSize -= strlen($established_header);
234
        // }
235
        break;
236
      }
237
    }
238
239
    if ($headerSize) {
240
      $responseBody = substr($respData, $headerSize);
241
      $responseHeaders = substr($respData, 0, $headerSize);
242
    } else {
243
      $responseSegments = explode("\r\n\r\n", $respData, 2);
244
      $responseHeaders = $responseSegments[0];
245
      $responseBody = isset($responseSegments[1]) ? $responseSegments[1] :
246
                                                    null;
247
    }
248
249
    $responseHeaders = $this->parseRawHeaders($responseHeaders);
250
251
    return array($responseHeaders, $responseBody);
252
  }
253
}
254