Passed
Push — master ( 7d790a...8d3c73 )
by Gino
02:09
created

CurlHelper::getRequestHandle()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 1
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace GinoPane\NanoRest\Supplemental;
4
5
use GinoPane\NanoRest\{
6
    Request\RequestContext,
7
    Exceptions\TransportException
8
};
9
10
class CurlHelper
11
{
12
    /**
13
     * Get a customized request handler to perform calls
14
     *
15
     * @param RequestContext $context
16
     *
17
     * @return resource
18
     */
19
    public function getRequestHandle(RequestContext $context)
20
    {
21
        $curlHandle = curl_init();
22
23
        $defaults = $this->getDefaultSettings($context);
24
25
        $defaults += $this->getCurlTimeoutSettings($context);
26
27
        $defaults += $this->getCurlSslSettings();
28
29
        $defaults += $this->getProxySettings($context);
30
31
        curl_setopt_array(
32
            $curlHandle, //@codeCoverageIgnore
33
            $context->getCurlOptions() + $defaults + $this->getRequestDataAndMethodOptions($context)
34
        );
35
36
        return $curlHandle;
37
    }
38
39
    /**
40
     * Execute curl handle
41
     *
42
     * @param resource $curlHandle
43
     *
44
     * @throws TransportException
45
     *
46
     * @return mixed
47
     */
48
    public function executeRequestHandle($curlHandle)
49
    {
50
        curl_setopt($curlHandle, CURLOPT_VERBOSE, true);
51
        $verbose = fopen('php://temp', 'w+');
52
        curl_setopt($curlHandle, CURLOPT_STDERR, $verbose);
53
54
        @list($headers, $response) = explode("\r\n\r\n", curl_exec($curlHandle), 2);
55
56
        $error = curl_error($curlHandle);
57
        $errorNumber = curl_errno($curlHandle);
58
        $httpStatus = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
59
60
        curl_close($curlHandle);
61
62
        $this->handleCurlError($error, $errorNumber, $verbose);
63
64
        return [
65
            'response' => $response,
66
            'httpStatus' => $httpStatus,
67
            'headers' => $headers
68
        ];
69
    }
70
71
    /**
72
     * Get SSL settings for CURL handler
73
     *
74
     * @link https://curl.haxx.se/docs/caextract.html
75
     *
76
     * @return array
77
     */
78
    private function getCurlSslSettings(): array
79
    {
80
        return [
81
            CURLOPT_SSL_VERIFYPEER  => true,
82
            CURLOPT_SSL_VERIFYHOST  => 2,
83
            CURLOPT_CAINFO          => ROOT_DIRECTORY . DIRECTORY_SEPARATOR . 'cacert.pem'
84
        ];
85
    }
86
87
    /**
88
     * Get proxy settings for cURL handle
89
     *
90
     * @param RequestContext $context
91
     * @return array
92
     */
93
    private function getProxySettings(RequestContext $context): array
94
    {
95
        $proxy = [];
96
97
        if (!is_null($context->getProxy())) {
98
            $proxy[CURLOPT_PROXY] = $context->getProxy();
99
        }
100
101
        return $proxy;
102
    }
103
104
    /**
105
     * Get timeout settings for CURL handler
106
     *
107
     * @link https://curl.haxx.se/libcurl/c/CURLOPT_TIMEOUT_MS.html
108
     * @link https://curl.haxx.se/libcurl/c/CURLOPT_CONNECTTIMEOUT_MS.html
109
     *
110
     * @param RequestContext $context
111
     *
112
     * @return array
113
     */
114
    private function getCurlTimeoutSettings(RequestContext $context): array
115
    {
116
        $timeoutOptions = [];
117
118
        $timeout = $context->getTimeout();
119
120
        $timeoutOptions += $this->fillTimeoutOptions($timeout, CURLOPT_TIMEOUT, CURLOPT_TIMEOUT_MS);
121
122
        $connectionTimeout = $context->getConnectionTimeout();
123
124
        $timeoutOptions += $this->fillTimeoutOptions(
125
            $connectionTimeout, //@codeCoverageIgnore
126
            CURLOPT_CONNECTTIMEOUT,
127
            CURLOPT_CONNECTTIMEOUT_MS
128
        );
129
130
        return $timeoutOptions;
131
    }
132
133
    /**
134
     * Get defaults settings for cURL handle
135
     *
136
     * @param RequestContext $context
137
     * @return array
138
     */
139
    private function getDefaultSettings(RequestContext $context): array
140
    {
141
        $defaults = [
142
            CURLOPT_ENCODING => "",
143
            CURLOPT_USERAGENT => "php-nano-rest",
144
            CURLOPT_HEADER => true,
145
            CURLOPT_HTTPHEADER => array_values($context->getRequestHeaders()),
146
            CURLOPT_RETURNTRANSFER => true,
147
        ];
148
149
        return $defaults;
150
    }
151
152
    /**
153
     * @param RequestContext $context
154
     *
155
     * @return array
156
     */
157
    private function getRequestDataAndMethodOptions(RequestContext $context)
158
    {
159
        $curlOptions = array();
160
161
        $requestData = $context->getData();
162
        $requestData = is_array($requestData) ? http_build_query($requestData) : $requestData;
163
164
        $url = $context->getRequestUri();
165
166
        switch ($context->getMethod()) {
167
            case RequestContext::METHOD_GET:
168
                $curlOptions[CURLOPT_HTTPGET] = 1;
169
                $url .= (strpos($url, '?') === false ? '?' : '') . $requestData;
170
                break;
171
            case RequestContext::METHOD_POST:
172
                $curlOptions[CURLOPT_POST] = 1;
173
                $curlOptions[CURLOPT_POSTFIELDS] = $requestData;
174
                break;
175
            default:
176
                $curlOptions[CURLOPT_CUSTOMREQUEST] = $context->getMethod();
177
                $curlOptions[CURLOPT_POSTFIELDS] = $requestData;
178
        }
179
180
        $curlOptions[CURLOPT_URL] = $url;
181
182
        return $curlOptions;
183
    }
184
185
    /**
186
     * @param $error
187
     * @param $errorNumber
188
     * @param $verbose
189
     *
190
     * @throws TransportException
191
     */
192
    private function handleCurlError($error, $errorNumber, $verbose): void
193
    {
194
        if (!$error) {
195
            return;
196
        }
197
198
        $errorMessage = "\ncURL transport error: $errorNumber - $error";
199
200
        $transportException = new TransportException($errorMessage);
201
202
        rewind($verbose);
203
204
        $verboseLog = stream_get_contents($verbose);
205
206
        if ($verboseLog !== false) {
207
            $verboseLog = htmlspecialchars($verboseLog);
208
209
            $transportException->setData($verboseLog);
210
        }
211
212
        throw $transportException;
213
    }
214
215
    /**
216
     * Fill timeout options
217
     *
218
     * @param $timeout
219
     * @param $optionName
220
     * @param $optionNameMs
221
     *
222
     * @return array
223
     */
224
    private function fillTimeoutOptions($timeout, $optionName, $optionNameMs): array
225
    {
226
        $timeoutOptions = [];
227
228
        if (is_int($timeout)) {
229
            $timeoutOptions[$optionName] = $timeout;
230
        } elseif (is_float($timeout)) {
231
            $timeoutOptions[$optionNameMs] = $timeout * 1000;
232
            $timeoutOptions[CURLOPT_NOSIGNAL] = 1;
233
        }
234
235
        return $timeoutOptions;
236
    }
237
}
238