Completed
Push — master ( d0e678...183caa )
by WEBEWEB
01:15
created

AbstractCurlRequest::parseHeader()   B

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