Passed
Push — master ( 36af2c...7d790a )
by Gino
02:06
created

CurlHelper::getProxySettings()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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