Passed
Pull Request — 1.x (#296)
by Akihito
02:49
created

HttpRequestCurl::request()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 1
eloc 15
c 4
b 0
f 0
nc 1
nop 3
dl 0
loc 19
rs 9.7666
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
34
/**
35
 * Sends a HTTP request using cURL
36
 *
37
 * @psalm-type RequestOptions = array<null>|array{"body?": string, "headers?": array<string, string>}
38
 * @psalm-type RequestHeaders = array<string, string>
39
 * @psalm-type Body = array<mixed>
40
 */
41
final class HttpRequestCurl implements HttpRequestInterface
42
{
43
    public function __construct(
44
        private HttpRequestHeaders $requestHeaders,
45
    ) {
46
    }
47
48
    /** @inheritdoc */
49
    public function request(string $method, string $uri, array $query): array
50
    {
51
        $body = http_build_query($query);
52
        $curl = $this->initializeCurl($method, $uri, $body);
53
        $response = (string) curl_exec($curl);
54
        $code = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
55
        $headerSize = (int) curl_getinfo($curl, CURLINFO_HEADER_SIZE);
56
        $headerString = substr($response, 0, $headerSize);
57
        $view = substr($response, $headerSize);
58
        $headers = $this->parseResponseHeaders($headerString);
59
        curl_close($curl);
60
61
        $body = $this->parseBody($curl, $view);
62
63
        return [
64
            'code' => $code,
65
            'headers' => $headers,
66
            'body' => $body,
67
            'view' => $view,
68
        ];
69
    }
70
71
    private function initializeCurl(string $method, string $uri, string $body): CurlHandle
72
    {
73
        $curl = curl_init();
74
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
75
        curl_setopt($curl, CURLOPT_URL, $uri);
76
77
        if ($this->requestHeaders->headers !== []) {
78
            curl_setopt($curl, CURLOPT_HTTPHEADER, $this->requestHeaders->headers);
79
        }
80
81
        if ($body !== '') {
82
            // Set the request body
83
            curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
84
        }
85
86
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
87
        curl_setopt($curl, CURLOPT_HEADER, true);
88
89
        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...
90
    }
91
92
    /** @return array<string, string> */
93
    private function parseResponseHeaders(string $responseHeaders): array
94
    {
95
        $responseHeadersArray = [];
96
        $headerLines = explode("\r\n", $responseHeaders);
97
        foreach ($headerLines as $line) {
98
            $parts = explode(':', $line, 2);
99
            if (count($parts) !== 2) {
100
                continue;
101
            }
102
103
            $key = $parts[0];
104
105
            $responseHeadersArray[$key] = trim($parts[1]);
106
        }
107
108
        return $responseHeadersArray;
109
    }
110
111
    /** @return array<mixed> */
112
    private function parseBody(CurlHandle $curl, string $view): array
113
    {
114
        $responseBody = [];
115
        $contentType = (string) curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
116
        if (strpos(strtolower($contentType), 'application/json') !== false) {
117
            return (array) json_decode($view, true);
118
        }
119
120
        return $responseBody;
121
    }
122
}
123