Passed
Pull Request — 1.x (#296)
by Akihito
03:06 queued 51s
created

HttpRequestCurl   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 80
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 39
c 5
b 0
f 0
dl 0
loc 80
rs 10
wmc 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A request() 0 19 1
A parseResponseHeaders() 0 16 3
A initializeCurl() 0 19 3
A parseBody() 0 9 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource;
6
7
use CurlHandle;
8
9
use function count;
10
use function curl_close;
11
use function curl_exec;
12
use function curl_getinfo;
13
use function curl_init;
14
use function curl_setopt;
15
use function explode;
16
use function http_build_query;
17
use function json_decode;
18
use function strpos;
19
use function strtolower;
20
use function substr;
21
use function trim;
22
23
use const CURLINFO_CONTENT_TYPE;
24
use const CURLINFO_HEADER_SIZE;
25
use const CURLINFO_HTTP_CODE;
26
use const CURLOPT_CUSTOMREQUEST;
27
use const CURLOPT_HEADER;
28
use const CURLOPT_HTTPHEADER;
29
use const CURLOPT_POSTFIELDS;
30
use const CURLOPT_RETURNTRANSFER;
31
use const CURLOPT_URL;
32
33
// ... Other code is omitted for brevity
34
35
/**
36
 * Sends a HTTP request using cURL
37
 *
38
 * @psalm-type RequestOptions = array<null>|array{"body?": string, "headers?": array<string, string>}
39
 * @psalm-type RequestHeaders = array<string, string>
40
 * @psalm-type Body = array<mixed>
41
 */
42
final class HttpRequestCurl implements HttpRequestInterface
43
{
44
    public function __construct(
45
        private HttpRequestHeaders $requestHeaders,
46
    ) {
47
    }
48
49
    /** @inheritdoc */
50
    public function request(string $method, string $uri, array $query): array
51
    {
52
        $body = http_build_query($query);
53
        $curl = $this->initializeCurl($method, $uri, $body);
54
        $response = (string) curl_exec($curl);
55
        $code = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
56
        $headerSize = (int) curl_getinfo($curl, CURLINFO_HEADER_SIZE);
57
        $headerString = substr($response, 0, $headerSize);
58
        $view = substr($response, $headerSize);
59
        $headers = $this->parseResponseHeaders($headerString);
60
        curl_close($curl);
61
62
        $body = $this->parseBody($curl, $view);
63
64
        return [
65
            'code' => $code,
66
            'headers' => $headers,
67
            'body' => $body,
68
            'view' => $view,
69
        ];
70
    }
71
72
    private function initializeCurl(string $method, string $uri, string $body): CurlHandle
73
    {
74
        $curl = curl_init();
75
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
76
        curl_setopt($curl, CURLOPT_URL, $uri);
77
78
        if ($this->requestHeaders->headers !== []) {
79
            curl_setopt($curl, CURLOPT_HTTPHEADER, $this->requestHeaders->headers);
80
        }
81
82
        if ($body !== '') {
83
            // Set the request body
84
            curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
85
        }
86
87
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
88
        curl_setopt($curl, CURLOPT_HEADER, true);
89
90
        return $curl;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $curl could return the type resource which is incompatible with the type-hinted return CurlHandle. Consider adding an additional type-check to rule them out.
Loading history...
91
    }
92
93
    /** @return array<string, string> */
94
    private function parseResponseHeaders(string $responseHeaders): array
95
    {
96
        $responseHeadersArray = [];
97
        $headerLines = explode("\r\n", $responseHeaders);
98
        foreach ($headerLines as $line) {
99
            $parts = explode(':', $line, 2);
100
            if (count($parts) !== 2) {
101
                continue;
102
            }
103
104
            $key = $parts[0];
105
106
            $responseHeadersArray[$key] = trim($parts[1]);
107
        }
108
109
        return $responseHeadersArray;
110
    }
111
112
    /** @return array<mixed> */
113
    private function parseBody(CurlHandle $curl, string $view): array
114
    {
115
        $responseBody = [];
116
        $contentType = (string) curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
117
        if (strpos(strtolower($contentType), 'application/json') !== false) {
118
            return (array) json_decode($view, true);
119
        }
120
121
        return $responseBody;
122
    }
123
}
124