HttpRequestCurl::parseResponseHeaders()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 9
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 16
rs 9.9666
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource;
6
7
use CurlHandle;
8
use Override;
9
use RuntimeException;
10
11
use function count;
12
use function curl_close;
13
use function curl_exec;
14
use function curl_getinfo;
15
use function curl_init;
16
use function curl_setopt;
17
use function explode;
18
use function http_build_query;
19
use function json_decode;
20
use function str_contains;
21
use function strtolower;
22
use function substr;
23
use function trim;
24
25
use const CURLINFO_CONTENT_TYPE;
26
use const CURLINFO_HEADER_SIZE;
27
use const CURLINFO_HTTP_CODE;
28
use const CURLOPT_CUSTOMREQUEST;
29
use const CURLOPT_HEADER;
30
use const CURLOPT_HTTPHEADER;
31
use const CURLOPT_POSTFIELDS;
32
use const CURLOPT_RETURNTRANSFER;
33
use const CURLOPT_URL;
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 readonly HttpRequestHeaders $requestHeaders,
46
    ) {
47
    }
48
49
    /** @inheritDoc */
50
    #[Override]
51
    public function request(string $method, string $uri, array $query): array
52
    {
53
        $body = http_build_query($query);
54
        $curl = $this->initializeCurl($method, $uri, $body);
55
        $response = (string) curl_exec($curl);
56
        $code = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
57
        $headerSize = (int) curl_getinfo($curl, CURLINFO_HEADER_SIZE);
58
        $headerString = substr($response, 0, $headerSize);
59
        $view = substr($response, $headerSize);
60
        $headers = $this->parseResponseHeaders($headerString);
61
        curl_close($curl);
62
63
        $body = $this->parseBody($curl, $view);
64
65
        return [
66
            'code' => $code,
67
            'headers' => $headers,
68
            'body' => $body,
69
            'view' => $view,
70
        ];
71
    }
72
73
    private function initializeCurl(string $method, string $uri, string $body): CurlHandle
74
    {
75
        $curl = curl_init();
76
        if ($curl === false) {
77
            throw new RuntimeException('Failed to initialize cURL'); // @codeCoverageIgnore
78
        }
79
80
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
81
        curl_setopt($curl, CURLOPT_URL, $uri);
82
83
        if ($this->requestHeaders->headers !== []) {
84
            curl_setopt($curl, CURLOPT_HTTPHEADER, $this->requestHeaders->headers);
85
        }
86
87
        if ($body !== '') {
88
            // Set the request body
89
            curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
90
        }
91
92
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
93
        curl_setopt($curl, CURLOPT_HEADER, true);
94
95
        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...
96
    }
97
98
    /** @return array<string, string> */
99
    private function parseResponseHeaders(string $responseHeaders): array
100
    {
101
        $responseHeadersArray = [];
102
        $headerLines = explode("\r\n", $responseHeaders);
103
        foreach ($headerLines as $line) {
104
            $parts = explode(':', $line, 2);
105
            if (count($parts) !== 2) {
106
                continue;
107
            }
108
109
            $key = $parts[0];
110
111
            $responseHeadersArray[$key] = trim($parts[1]);
112
        }
113
114
        return $responseHeadersArray;
115
    }
116
117
    /** @return array<mixed> */
118
    private function parseBody(CurlHandle $curl, string $view): array
119
    {
120
        $responseBody = [];
121
        $contentType = (string) curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
122
        if (str_contains(strtolower($contentType), 'application/json')) {
123
            return (array) json_decode($view, true);
124
        }
125
126
        return $responseBody;
127
    }
128
}
129