AbstractCurlRequest::parseHeader()   B
last analyzed

Complexity

Conditions 7
Paths 7

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 8.5386
c 0
b 0
f 0
cc 7
nc 7
nop 1
1
<?php
2
3
/*
4
 * This file is part of the core-library package.
5
 *
6
 * (c) 2018 WEBEWEB
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace WBW\Library\Core\Network\CURL\Request;
13
14
use DateTime;
15
use InvalidArgumentException;
16
use WBW\Library\Core\Network\CURL\API\CurlRequestInterface;
17
use WBW\Library\Core\Network\CURL\API\CurlResponseInterface;
18
use WBW\Library\Core\Network\CURL\Configuration\CurlConfiguration;
19
use WBW\Library\Core\Network\CURL\Exception\CurlRequestCallException;
20
use WBW\Library\Core\Network\CURL\Factory\CurlFactory;
21
use WBW\Library\Core\Network\CURL\Helper\CurlHelper;
22
use WBW\Library\Core\Network\HTTP\HttpInterface;
23
24
/**
25
 * Abstract cURL request.
26
 *
27
 * @author webeweb <https://github.com/webeweb/>
28
 * @package WBW\Library\Core\Network\CURL\Request
29
 * @abstract
30
 */
31
abstract class AbstractCurlRequest implements CurlRequestInterface, HttpInterface {
32
33
    /**
34
     * Configuration.
35
     *
36
     * @var CurlConfiguration
37
     */
38
    private $configuration;
39
40
    /**
41
     * Headers.
42
     *
43
     * @var array
44
     */
45
    private $headers;
46
47
    /**
48
     * Method.
49
     *
50
     * @var string
51
     */
52
    private $method;
53
54
    /**
55
     * POST data.
56
     *
57
     * @var array
58
     */
59
    private $postData;
60
61
    /**
62
     * Query data.
63
     *
64
     * @var array
65
     */
66
    private $queryData;
67
68
    /**
69
     * Resource path.
70
     *
71
     * @var string
72
     */
73
    private $resourcePath;
74
75
    /**
76
     * Constructor.
77
     *
78
     * @param string $method The Method.
79
     * @param CurlConfiguration $configuration The configuration.
80
     * @param string|null $resourcePath The resource path.
81
     * @throws InvalidArgumentException Throws an invalid argument exception if the method is invalid.
82
     */
83
    protected function __construct(string $method, CurlConfiguration $configuration, ?string $resourcePath) {
84
        $this->setConfiguration($configuration);
85
        $this->setHeaders([]);
86
        $this->setMethod($method);
87
        $this->setQueryData([]);
88
        $this->setPostData([]);
89
        $this->setResourcePath($resourcePath);
90
    }
91
92
    /**
93
     * {@inheritDoc}
94
     */
95
    public function addHeader(string $name, string $value): CurlRequestInterface {
96
        $this->headers[$name] = $value;
97
        return $this;
98
    }
99
100
    /**
101
     * {@inheritDoc}
102
     */
103
    public function addPostData(string $name, string $value): CurlRequestInterface {
104
        $this->postData[$name] = $value;
105
        return $this;
106
    }
107
108
    /**
109
     * {@inheritDoc}
110
     */
111
    public function addQueryData(string $name, string $value): CurlRequestInterface {
112
        $this->queryData[$name] = $value;
113
        return $this;
114
    }
115
116
    /**
117
     * {@inheritDoc}
118
     */
119
    public function call(): CurlResponseInterface {
120
121
        $requestHeader = $this->mergeHeaders();
122
        $requestBody   = http_build_query($this->getPostData());
123
124
        if (true === in_array("Content-Type: application/json", $requestHeader)) {
125
            $requestBody = json_encode($this->getPostData());
126
        }
127
128
        $requestUrl = $this->mergeUrl();
129
        if (0 < count($this->getQueryData())) {
130
            $requestUrl = implode("?", [$requestUrl, http_build_query($this->getQueryData())]);
131
        }
132
133
        $stream = CurlHelper::initStream($requestUrl, $this->getConfiguration());
134
135
        CurlHelper::setHeaders($stream, $requestHeader);
136
        CurlHelper::setPost($stream, $this->getMethod(), $requestBody);
137
        CurlHelper::setProxy($stream, $this->getConfiguration());
138
        CurlHelper::setReturnTransfer($stream);
139
        CurlHelper::setSsl($stream, $this->getConfiguration());
140
        CurlHelper::setTimeout($stream, $this->getConfiguration());
141
        CurlHelper::setUserAgent($stream, $this->getConfiguration());
142
        CurlHelper::setVerbose($stream, $this->getConfiguration(), $requestUrl, $requestBody);
143
144
        $curlExec    = curl_exec($stream);
145
        $curlGetInfo = curl_getinfo($stream, CURLINFO_HEADER_SIZE);
146
147
        $responseHeader = $this->parseheader(substr($curlExec, 0, $curlGetInfo));
148
        $responseBody   = substr($curlExec, $curlGetInfo);
149
        $responseInfo   = curl_getinfo($stream);
150
151
        if (true === $this->getConfiguration()->getDebug()) {
152
            $msg = (new DateTime())->format("c") . " [DEBUG] {$requestUrl}" . PHP_EOL . "HTTP response body ~BEGIN~" . PHP_EOL . print_r($responseBody, true) . PHP_EOL . "~END~" . PHP_EOL;
153
            error_log($msg, 3, $this->getConfiguration()->getDebugFile());
154
        }
155
156
        $response = $this->prepareResponse($requestBody, $requestHeader, $requestUrl, $responseBody, $responseHeader, $responseInfo);
157
158
        $curlHttpCode = $responseInfo["http_code"];
159
        if (200 <= $curlHttpCode && $curlHttpCode <= 299) {
160
            return $response;
161
        }
162
163
        $msg = curl_errno($stream);
164
        if (0 === $curlHttpCode) {
165
            if (false === empty(curl_error($stream))) {
166
                $msg = "Call to {$requestUrl} failed : " . curl_error($stream);
167
            } else {
168
                $msg = "Call to {$requestUrl} failed, but for an unknown reason. This could happen if you are disconnected from the network.";
169
            }
170
        }
171
172
        throw new CurlRequestCallException($msg, $curlHttpCode, $response);
173
    }
174
175
    /**
176
     * {@inheritDoc}
177
     */
178
    public function clearHeaders(): CurlRequestInterface {
179
        return $this->setHeaders([]);
180
    }
181
182
    /**
183
     * {@inheritDoc}
184
     */
185
    public function clearPostData(): CurlRequestInterface {
186
        return $this->setPostData([]);
187
    }
188
189
    /**
190
     * {@inheritDoc}
191
     */
192
    public function clearQueryData(): CurlRequestInterface {
193
        return $this->setQueryData([]);
194
    }
195
196
    /**
197
     * {@inheritDoc}
198
     */
199
    public function getConfiguration(): CurlConfiguration {
200
        return $this->configuration;
201
    }
202
203
    /**
204
     * {@inheritDoc}
205
     */
206
    public function getHeaders(): array {
207
        return $this->headers;
208
    }
209
210
    /**
211
     * {@inheritDoc}
212
     */
213
    public function getMethod(): string {
214
        return $this->method;
215
    }
216
217
    /**
218
     * {@inheritDoc}
219
     */
220
    public function getPostData(): array {
221
        return $this->postData;
222
    }
223
224
    /**
225
     * {@inheritDoc}
226
     */
227
    public function getQueryData(): array {
228
        return $this->queryData;
229
    }
230
231
    /**
232
     * {@inheritDoc}
233
     */
234
    public function getResourcePath(): string {
235
        return $this->resourcePath;
236
    }
237
238
    /**
239
     * Merge the headers.
240
     *
241
     * @return array Returns the merged headers.
242
     */
243
    private function mergeHeaders(): array {
244
245
        $headers = [];
246
        foreach (array_merge($this->getConfiguration()->getHeaders(), $this->getHeaders()) as $key => $value) {
247
            $headers[] = implode(": ", [$key, $value]);
248
        }
249
250
        return $headers;
251
    }
252
253
    /**
254
     * Merge the URL.
255
     *
256
     * @return string Returns the merged URL.
257
     */
258
    private function mergeUrl(): string {
259
260
        $mergedURL = [
261
            $this->getConfiguration()->getHost(),
262
        ];
263
264
        if (null !== $this->getResourcePath() && "" !== $this->getResourcePath()) {
265
            $mergedURL[] = $this->getResourcePath();
266
        }
267
268
        return implode("/", $mergedURL);
269
    }
270
271
    /**
272
     * Parse the raw header.
273
     *
274
     * @param string $rawHeader The raw header.
275
     * @return array Returns the headers.
276
     */
277
    private function parseHeader(string $rawHeader): array {
278
279
        $headers = [];
280
        $key     = "";
281
282
        foreach (explode("\n", $rawHeader) as $h) {
283
            $h = explode(":", $h, 2);
284
            if (true === isset($h[1])) {
285
                if (false === isset($headers[$h[0]])) {
286
                    $headers[$h[0]] = trim($h[1]);
287
                } else if (true === is_array($headers[$h[0]])) {
288
                    $headers[$h[0]] = array_merge($headers[$h[0]], [trim($h[1])]);
289
                } else {
290
                    $headers[$h[0]] = array_merge([$headers[$h[0]]], [trim($h[1])]);
291
                }
292
                $key = $h[0];
293
            } else {
294
                if ("\t" === substr($h[0], 0, 1)) {
295
                    $headers[$key] .= "\r\n\t" . trim($h[0]);
296
                } else if (!$key) {
297
                    $headers[0] = trim($h[0]);
298
                }
299
                trim($h[0]);
300
            }
301
        }
302
303
        return $headers;
304
    }
305
306
    /**
307
     * Prepare a response.
308
     *
309
     * @param string $requestBody The request body.
310
     * @param array $requestHeader The request header.
311
     * @param string $requestUri The request URI.
312
     * @param string $responseBody The response body.
313
     * @param array $responseHeader The response header.
314
     * @param array $responseInfo The response info.
315
     * @return CurlResponseInterface Returns the response.
316
     */
317
    private function prepareResponse(string $requestBody, array $requestHeader, string $requestUri, string $responseBody, array $responseHeader, array $responseInfo): CurlResponseInterface {
318
319
        $response = CurlFactory::newCURLResponse();
320
        $response->setRequestBody($requestBody);
321
        $response->setRequestHeader($requestHeader);
322
        $response->setRequestUrl($requestUri);
323
        $response->setResponseBody($responseBody);
324
        $response->setResponseHeader($responseHeader);
325
        $response->setResponseInfo($responseInfo);
326
327
        return $response;
328
    }
329
330
    /**
331
     * {@inheritDoc}
332
     */
333
    public function removeHeader(string $name): CurlRequestInterface {
334
        if (true === array_key_exists($name, $this->headers)) {
335
            unset($this->headers[$name]);
336
        }
337
        return $this;
338
    }
339
340
    /**
341
     * {@inheritDoc}
342
     */
343
    public function removePostData(string $name): CurlRequestInterface {
344
        if (true === array_key_exists($name, $this->postData)) {
345
            unset($this->postData[$name]);
346
        }
347
        return $this;
348
    }
349
350
    /**
351
     * {@inheritDoc}
352
     */
353
    public function removeQueryData(string $name): CurlRequestInterface {
354
        if (true === array_key_exists($name, $this->queryData)) {
355
            unset($this->queryData[$name]);
356
        }
357
        return $this;
358
    }
359
360
    /**
361
     * Set the configuration.
362
     *
363
     * @param CurlConfiguration $configuration The configuration.
364
     * @return CurlRequestInterface Returns this cURL request.
365
     */
366
    protected function setConfiguration(CurlConfiguration $configuration): CurlRequestInterface {
367
        $this->configuration = $configuration;
368
        return $this;
369
    }
370
371
    /**
372
     * Set the headers.
373
     *
374
     * @param array $headers The headers.
375
     * @return CurlRequestInterface Returns this cURL request.
376
     */
377
    protected function setHeaders(array $headers): CurlRequestInterface {
378
        $this->headers = $headers;
379
        return $this;
380
    }
381
382
    /**
383
     * Set the method.
384
     *
385
     * @param string $method The method.
386
     * @return CurlRequestInterface Returns this cURL request.
387
     * @throws InvalidArgumentException Throws an invalid argument exception if the method is invalid.
388
     */
389
    protected function setMethod(string $method): CurlRequestInterface {
390
391
        switch ($method) {
392
            case self::HTTP_METHOD_DELETE:
393
            case self::HTTP_METHOD_GET:
394
            case self::HTTP_METHOD_HEAD:
395
            case self::HTTP_METHOD_OPTIONS:
396
            case self::HTTP_METHOD_PATCH:
397
            case self::HTTP_METHOD_POST:
398
            case self::HTTP_METHOD_PUT:
399
                $this->method = $method;
400
                break;
401
            default:
402
                throw new InvalidArgumentException(sprintf('The HTTP method "%s" is invalid', $method));
403
        }
404
405
        return $this;
406
    }
407
408
    /**
409
     * Set the POST data.
410
     *
411
     * @param array $postData The POST data.
412
     * @return CurlRequestInterface Returns this cURL request.
413
     */
414
    protected function setPostData(array $postData): CurlRequestInterface {
415
        $this->postData = $postData;
416
        return $this;
417
    }
418
419
    /**
420
     * Set the query data.
421
     *
422
     * @param array $queryData The query data.
423
     * @return CurlRequestInterface Returns this cURL request.
424
     */
425
    protected function setQueryData(array $queryData): CurlRequestInterface {
426
        $this->queryData = $queryData;
427
        return $this;
428
    }
429
430
    /**
431
     * {@inheritDoc}
432
     */
433
    public function setResourcePath(?string $resourcePath): CurlRequestInterface {
434
        $this->resourcePath = preg_replace("/^\//", "", trim($resourcePath));
435
        return $this;
436
    }
437
}
438