Completed
Push — master ( 1c956a...362a13 )
by WEBEWEB
01:17
created

AbstractCURLRequest::setCurlPost()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 8.6026
c 0
b 0
f 0
cc 7
nc 7
nop 2
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 Exception;
16
use InvalidArgumentException;
17
use WBW\Library\Core\Argument\ArgumentHelper;
18
use WBW\Library\Core\Network\CURL\API\CURLRequestInterface;
19
use WBW\Library\Core\Network\CURL\Configuration\CURLConfiguration;
20
use WBW\Library\Core\Network\CURL\Exception\CURLRequestCallException;
21
use WBW\Library\Core\Network\CURL\Factory\CURLFactory;
22
use WBW\Library\Core\Network\CURL\Response\CURLResponse;
23
use WBW\Library\Core\Network\HTTP\HTTPInterface;
24
25
/**
26
 * Abstract cURL request.
27
 *
28
 * @author webeweb <https://github.com/webeweb/>
29
 * @package WBW\Library\Core\Network\CURL\Request
30
 * @abstract
31
 */
32
abstract class AbstractCURLRequest implements CURLRequestInterface, HTTPInterface {
33
34
    /**
35
     * Content-Type "application/x-www-form-urlencoded".
36
     * @var string
37
     */
38
    const CONTENT_TYPE_X_WWW_FORM_URLENCODED = "Content-Type: application/x-www-form-urlencoded";
39
40
    /**
41
     * Configuration.
42
     *
43
     * @var CURLConfiguration
44
     */
45
    private $configuration;
46
47
    /**
48
     * Headers.
49
     *
50
     * @var array
51
     */
52
    private $headers = [];
53
54
    /**
55
     * Method.
56
     *
57
     * @var string
58
     */
59
    private $method = self::HTTP_METHOD_GET;
60
61
    /**
62
     * POST data.
63
     *
64
     * @var array
65
     */
66
    private $postData = [];
67
68
    /**
69
     * Query data.
70
     *
71
     * @var array
72
     */
73
    private $queryData = [];
74
75
    /**
76
     * Resource path.
77
     *
78
     * @var string
79
     */
80
    private $resourcePath;
81
82
    /**
83
     * Constructor.
84
     *
85
     * @param string $method The Method.
86
     * @param CURLConfiguration $configuration The configuration.
87
     * @param string $resourcePath The resource path.
88
     * @throws InvalidArgumentException Throws an invalid argument exception if the method is not implemented.
89
     */
90
    protected function __construct($method, CURLConfiguration $configuration, $resourcePath) {
91
        $this->setConfiguration($configuration);
92
        $this->setMethod($method);
93
        $this->setResourcePath($resourcePath);
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function addHeader($name, $value) {
100
        ArgumentHelper::isTypeOf($name, ArgumentHelper::ARGUMENT_STRING);
101
        $this->headers[$name] = $value;
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
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function addQueryData($name, $value) {
116
        ArgumentHelper::isTypeOf($name, ArgumentHelper::ARGUMENT_STRING);
117
        $this->queryData[$name] = $value;
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function call() {
124
125
        $curlHeaders  = $this->mergeHeaders();
126
        $curlPOSTData = http_build_query($this->getPostData());
127
128
        if (true === in_array("Content-Type: application/json", $curlHeaders)) {
129
            $curlPOSTData = json_encode($this->getPostData());
130
        }
131
132
        $curlURL = $this->mergeURL();
133
        if (0 < count($this->getQueryData())) {
134
            $curlURL = implode("?", [$curlURL, http_build_query($this->getQueryData())]);
135
        }
136
137
        $stream = curl_init();
138
139
        if (0 < $this->getConfiguration()->getConnectTimeout()) {
140
            curl_setopt($stream, CURLOPT_CONNECTTIMEOUT, $this->getConfiguration()->getConnectTimeout());
141
        }
142
143
        if (true === $this->getConfiguration()->getAllowEncoding()) {
144
            curl_setopt($stream, CURLOPT_ENCODING, "");
145
        }
146
147
        curl_setopt($stream, CURLOPT_HEADER, 1);
148
        curl_setopt($stream, CURLOPT_HTTPHEADER, $curlHeaders);
149
150
        $this->setCurlPost($stream, $curlPOSTData);
151
        $this->setCURLProxy($stream);
152
        $this->setCurlReturnTransfer($stream);
153
        $this->setCurlSsl($stream);
154
        $this->setCurlTimeout($stream);
155
156
        curl_setopt($stream, CURLOPT_URL, $curlURL);
157
158
        $this->setCurlUserAgent($stream);
159
        $this->setCurlVerbose($stream, $curlURL, $curlPOSTData);
160
161
        $curlExec     = curl_exec($stream);
162
        $httpHeadSize = curl_getinfo($stream, CURLINFO_HEADER_SIZE);
163
        $httpHead     = $this->parseheader(substr($curlExec, 0, $httpHeadSize));
164
        $httpBody     = substr($curlExec, $httpHeadSize);
165
        $curlInfo     = curl_getinfo($stream);
166
167
        if (true === $this->getConfiguration()->getDebug()) {
168
            $msg = (new DateTime())->format("c") . " [DEBUG] " . $curlURL . PHP_EOL . "HTTP response body ~BEGIN~" . PHP_EOL . print_r($httpBody, true) . PHP_EOL . "~END~" . PHP_EOL;
169
            error_log($msg, 3, $this->getConfiguration()->getDebugFile());
170
        }
171
172
        $response = CURLFactory::newCURLResponse();
173
        $response->setRequestBody($curlPOSTData);
174
        $response->setRequestHeader($curlHeaders);
175
        $response->setRequestURL($curlURL);
176
        $response->setResponseBody($httpBody);
177
        $response->setResponseHeader($httpHead);
178
        $response->setResponseInfo($curlInfo);
179
180
        if (200 <= $curlInfo["http_code"] && $curlInfo["http_code"] <= 299) {
181
            return $response;
182
        }
183
184
        $cde = $curlInfo["http_code"];
185
        $msg = curl_errno($stream);
186
187
        if (0 === $curlInfo["http_code"]) {
188
            if (false === empty(curl_error($stream))) {
189
                $msg = "Call to ${curlURL} failed : " . curl_error($stream);
190
            } else {
191
                $msg = "Call to ${curlURL} failed, but for an unknown reason. This could happen if you are disconnected from the network.";
192
            }
193
        }
194
195
        throw new CURLRequestCallException($msg, $cde, $response);
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201
    public function clearHeaders() {
202
        return $this->setHeaders([]);
203
    }
204
205
    /**
206
     * {@inheritdoc}
207
     */
208
    public function clearPostData() {
209
        return $this->setPostData([]);
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    public function clearQueryData() {
216
        return $this->setQueryData([]);
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222
    public function getConfiguration() {
223
        return $this->configuration;
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229
    public function getHeaders() {
230
        return $this->headers;
231
    }
232
233
    /**
234
     * {@inheritdoc}
235
     */
236
    public function getMethod() {
237
        return $this->method;
238
    }
239
240
    /**
241
     * {@inheritdoc}
242
     */
243
    public function getPostData() {
244
        return $this->postData;
245
    }
246
247
    /**
248
     * {@inheritdoc}
249
     */
250
    public function getQueryData() {
251
        return $this->queryData;
252
    }
253
254
    /**
255
     * {@inheritdoc}
256
     */
257
    public function getResourcePath() {
258
        return $this->resourcePath;
259
    }
260
261
    /**
262
     * Merge the headers.
263
     *
264
     * @return array Returns the merged headers.
265
     */
266
    private function mergeHeaders() {
267
268
        $mergedHeaders = [];
269
        foreach (array_merge($this->getConfiguration()->getHeaders(), $this->getHeaders()) as $key => $value) {
270
            $mergedHeaders[] = implode(": ", [$key, $value]);
271
        }
272
273
        return $mergedHeaders;
274
    }
275
276
    /**
277
     * Merge the URL.
278
     *
279
     * @return string Returns the merged URL.
280
     */
281
    private function mergeURL() {
282
283
        $mergedURL = [
284
            $this->getConfiguration()->getHost(),
285
        ];
286
287
        if (null !== $this->getResourcePath() && "" !== $this->getResourcePath()) {
288
            $mergedURL[] = $this->getResourcePath();
289
        }
290
291
        return implode("/", $mergedURL);
292
    }
293
294
    /**
295
     * Parse the raw header.
296
     *
297
     * @param string $rawHeader The raw header.
298
     * @return array Returns the headers.
299
     */
300
    private function parseHeader($rawHeader) {
301
302
        $headers = [];
303
        $key     = "";
304
305
        foreach (explode("\n", $rawHeader) as $h) {
306
            $h = explode(":", $h, 2);
307
            if (true === isset($h[1])) {
308
                if (false === isset($headers[$h[0]])) {
309
                    $headers[$h[0]] = trim($h[1]);
310
                } else if (true === is_array($headers[$h[0]])) {
311
                    $headers[$h[0]] = array_merge($headers[$h[0]], [trim($h[1])]);
312
                } else {
313
                    $headers[$h[0]] = array_merge([$headers[$h[0]]], [trim($h[1])]);
314
                }
315
                $key = $h[0];
316
            } else {
317
                if ("\t" === substr($h[0], 0, 1)) {
318
                    $headers[$key] .= "\r\n\t" . trim($h[0]);
319
                } else if (!$key) {
320
                    $headers[0] = trim($h[0]);
321
                }
322
                trim($h[0]);
323
            }
324
        }
325
326
        return $headers;
327
    }
328
329
    /**
330
     * {@inheritdoc}
331
     */
332
    public function removeHeader($name) {
333
        if (true === array_key_exists($name, $this->headers)) {
334
            unset($this->headers[$name]);
335
        }
336
        return $this;
337
    }
338
339
    /**
340
     * {@inheritdoc}
341
     */
342
    public function removePostData($name) {
343
        if (true === array_key_exists($name, $this->postData)) {
344
            unset($this->postData[$name]);
345
        }
346
        return $this;
347
    }
348
349
    /**
350
     * {@inheritdoc}
351
     */
352
    public function removeQueryData($name) {
353
        if (true === array_key_exists($name, $this->queryData)) {
354
            unset($this->queryData[$name]);
355
        }
356
        return $this;
357
    }
358
359
    /**
360
     * Set the configuration.
361
     *
362
     * @param CURLConfiguration $configuration The configuration.
363
     * @return CURLRequestInterface Returns this cURL request.
364
     */
365
    protected function setConfiguration(CURLConfiguration $configuration) {
366
        $this->configuration = $configuration;
367
        return $this;
368
    }
369
370
    /**
371
     * Set cURL POST.
372
     *
373
     * @param resource $stream The stream.
374
     * @param string $postData The POST data.
375
     * @return CURLRequestInterface Returns this cURL request.
376
     */
377
    protected function setCurlPost($stream, $postData) {
378
379
        switch ($this->getMethod()) {
380
381
            case self::HTTP_METHOD_DELETE:
382
            case self::HTTP_METHOD_OPTIONS:
383
            case self::HTTP_METHOD_PATCH:
384
            case self::HTTP_METHOD_PUT:
385
                curl_setopt($stream, CURLOPT_CUSTOMREQUEST, $this->getMethod());
386
                curl_setopt($stream, CURLOPT_POSTFIELDS, $postData);
387
                break;
388
389
            case self::HTTP_METHOD_HEAD:
390
                curl_setopt($stream, CURLOPT_NOBODY, true);
391
                break;
392
393
            case self::HTTP_METHOD_POST:
394
                curl_setopt($stream, CURLOPT_POST, true);
395
                curl_setopt($stream, CURLOPT_POSTFIELDS, $postData);
396
                break;
397
        }
398
399
        return $this;
400
    }
401
402
    /**
403
     * Set cURL proxy.
404
     *
405
     * @param resource $stream The stream.
406
     * @return CURLRequestInterface Returns this cURL request.
407
     */
408
    protected function setCurlProxy($stream) {
409
410
        if (null !== $this->getConfiguration()->getProxyHost()) {
411
            curl_setopt($stream, CURLOPT_PROXY, $this->getConfiguration()->getProxyHost());
412
        }
413
414
        if (null !== $this->getConfiguration()->getProxyPort()) {
415
            curl_setopt($stream, CURLOPT_PROXYPORT, $this->getConfiguration()->getProxyPort());
416
        }
417
418
        if (null !== $this->getConfiguration()->getProxyType()) {
419
            curl_setopt($stream, CURLOPT_PROXYTYPE, $this->getConfiguration()->getProxyType());
420
        }
421
422
        if (null !== $this->getConfiguration()->getProxyUsername()) {
423
            curl_setopt($stream, CURLOPT_PROXYUSERPWD, implode(":", [$this->getConfiguration()->getProxyUsername(), $this->getConfiguration()->getProxyPassword()]));
424
        }
425
426
        return $this;
427
    }
428
429
    /**
430
     * Set cURL return transfer.
431
     *
432
     * @param resource $stream The stream.
433
     * @return CURLRequestInterface Returns this cURL request.
434
     */
435
    protected function setCurlReturnTransfer($stream) {
436
        curl_setopt($stream, CURLOPT_RETURNTRANSFER, true);
437
        return $this;
438
    }
439
440
    /**
441
     * Set cURL SSL.
442
     *
443
     * @param resource $stream The stream.
444
     * @return CURLRequestInterface Returns this cURL request.
445
     */
446
    protected function setCurlSsl($stream) {
447
448
        if (false === $this->getConfiguration()->getSslVerification()) {
449
            curl_setopt($stream, CURLOPT_SSL_VERIFYHOST, 0);
450
            curl_setopt($stream, CURLOPT_SSL_VERIFYPEER, 0);
451
        }
452
453
        return $this;
454
    }
455
456
    /**
457
     * Set cURL timeout.
458
     *
459
     * @param resource $stream The stream.
460
     * @return CURLRequestInterface Returns this cURL request.
461
     */
462
    protected function setCurlTimeout($stream) {
463
464
        if (0 < $this->getConfiguration()->getRequestTimeout()) {
465
            curl_setopt($stream, CURLOPT_TIMEOUT, $this->getConfiguration()->getRequestTimeout());
466
        }
467
468
        return $this;
469
    }
470
471
    /**
472
     * Set cURL user agent.
473
     *
474
     * @param resource $stream The stream.
475
     * @return CURLRequestInterface Returns this cURL request.
476
     */
477
    protected function setCurlUserAgent($stream) {
478
        curl_setopt($stream, CURLOPT_USERAGENT, $this->getConfiguration()->getUserAgent());
479
        return $this;
480
    }
481
482
    /**
483
     * Set cURL verbose.
484
     *
485
     * @param resource $stream The stream.
486
     * @param string $url The URL.
487
     * @param string $postData The POST data.
488
     * @return CURLRequestInterface Returns this cURL request.
489
     * @throws Exception Throws an exception if an error occurs.
490
     */
491
    protected function setCurlVerbose($stream, $url, $postData) {
492
493
        if (true === $this->getConfiguration()->getDebug()) {
494
495
            curl_setopt($stream, CURLOPT_STDERR, fopen($this->getConfiguration()->getDebugFile(), "a"));
496
            curl_setopt($stream, CURLOPT_VERBOSE, 0);
497
498
            $msg = (new DateTime())->format("c") . " [DEBUG] " . $url . PHP_EOL . "HTTP request body ~BEGIN~" . PHP_EOL . print_r($postData, true) . PHP_EOL . "~END~" . PHP_EOL;
499
            error_log($msg, 3, $this->getConfiguration()->getDebugFile());
500
        } else {
501
502
            if (true === $this->getConfiguration()->getVerbose()) {
503
                curl_setopt($stream, CURLOPT_VERBOSE, 1);
504
            } else {
505
                curl_setopt($stream, CURLOPT_VERBOSE, 0);
506
            }
507
        }
508
509
        return $this;
510
    }
511
512
    /**
513
     * Set the headers.
514
     *
515
     * @param array $headers The headers.
516
     * @return CURLRequestInterface Returns this cURL request.
517
     */
518
    protected function setHeaders(array $headers) {
519
        $this->headers = $headers;
520
        return $this;
521
    }
522
523
    /**
524
     * Set the method.
525
     *
526
     * @param string $method The method.
527
     * @return CURLRequestInterface Returns this cURL request.
528
     * @throws InvalidArgumentException Throws an invalid argument exception if the method is not implemented.
529
     */
530
    protected function setMethod($method) {
531
        switch ($method) {
532
            case self::HTTP_METHOD_DELETE:
533
            case self::HTTP_METHOD_GET:
534
            case self::HTTP_METHOD_HEAD:
535
            case self::HTTP_METHOD_OPTIONS:
536
            case self::HTTP_METHOD_PATCH:
537
            case self::HTTP_METHOD_POST:
538
            case self::HTTP_METHOD_PUT:
539
                $this->method = $method;
540
                break;
541
            default:
542
                throw new InvalidArgumentException(sprintf("The HTTP method \"%s\" is invalid", $method));
543
        }
544
        return $this;
545
    }
546
547
    /**
548
     * Set the POST data.
549
     *
550
     * @param array $postData The POST data.
551
     * @return CURLRequestInterface Returns this cURL request.
552
     */
553
    protected function setPostData(array $postData) {
554
        $this->postData = $postData;
555
        return $this;
556
    }
557
558
    /**
559
     * Set the query data.
560
     *
561
     * @param array $queryData The query data.
562
     * @return CURLRequestInterface Returns this cURL request.
563
     */
564
    protected function setQueryData(array $queryData) {
565
        $this->queryData = $queryData;
566
        return $this;
567
    }
568
569
    /**
570
     * {@inheritdoc}
571
     */
572
    public function setResourcePath($resourcePath) {
573
        $this->resourcePath = preg_replace("/^\//", "", trim($resourcePath));
574
        return $this;
575
    }
576
}
577