Passed
Push — master ( d33186...0b1c5d )
by Gino
04:38
created

CurlHelper::getCurlTimeoutSettings()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 1
dl 0
loc 17
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 = [
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
        if (!is_null($context->getProxy())) {
36
            $defaults[CURLOPT_PROXY] = $context->getProxy();
37
        }
38
39
        $dataAndMethodOptions = $this->getRequestDataAndMethodOptions($context);
40
41
        curl_setopt_array($curlHandle, $context->getCurlOptions() + $defaults + $dataAndMethodOptions);
42
43
        return $curlHandle;
44
    }
45
46
    /**
47
     * Execute curl handle
48
     *
49
     * @param resource $curlHandle
50
     *
51
     * @throws TransportException
52
     *
53
     * @return mixed
54
     */
55
    public function executeRequestHandle($curlHandle)
56
    {
57
        curl_setopt($curlHandle, CURLOPT_VERBOSE, true);
58
        $verbose = fopen('php://temp', 'w+');
59
        curl_setopt($curlHandle, CURLOPT_STDERR, $verbose);
60
61
        @list($headers, $response) = explode("\r\n\r\n", curl_exec($curlHandle), 2);
62
63
        $error = curl_error($curlHandle);
64
        $errorNumber = curl_errno($curlHandle);
65
        $httpStatus = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
66
67
        curl_close($curlHandle);
68
69
        $this->handleCurlError($error, $errorNumber, $verbose);
70
71
        return [
72
            'response' => $response,
73
            'httpStatus' => $httpStatus,
74
            'headers' => $headers
75
        ];
76
    }
77
78
    /**
79
     * Get SSL settings for CURL handler
80
     *
81
     * @link https://curl.haxx.se/docs/caextract.html
82
     *
83
     * @return array
84
     */
85
    private function getCurlSslSettings(): array
86
    {
87
        return [
88
            CURLOPT_SSL_VERIFYPEER  => true,
89
            CURLOPT_SSL_VERIFYHOST  => 2,
90
            CURLOPT_CAINFO          => ROOT_DIRECTORY . DIRECTORY_SEPARATOR . 'cacert.pem'
91
        ];
92
    }
93
94
    /**
95
     * Get timeout settings for CURL handler
96
     *
97
     * @link https://curl.haxx.se/libcurl/c/CURLOPT_TIMEOUT_MS.html
98
     * @link https://curl.haxx.se/libcurl/c/CURLOPT_CONNECTTIMEOUT_MS.html
99
     *
100
     * @param RequestContext $context
101
     *
102
     * @return array
103
     */
104
    private function getCurlTimeoutSettings(RequestContext $context): array
105
    {
106
        $timeoutOptions = [];
107
108
        $timeout = $context->getTimeout();
109
110
        $timeoutOptions += $this->fillTimeoutOptions($timeout, CURLOPT_TIMEOUT, CURLOPT_TIMEOUT_MS);
111
112
        $connectionTimeout = $context->getConnectionTimeout();
113
114
        $timeoutOptions += $this->fillTimeoutOptions(
115
            $connectionTimeout, //@codeCoverageIgnore
116
            CURLOPT_CONNECTTIMEOUT,
117
            CURLOPT_CONNECTTIMEOUT_MS
118
        );
119
120
        return $timeoutOptions;
121
    }
122
123
    /**
124
     * @param RequestContext $context
125
     *
126
     * @return array
127
     */
128
    private function getRequestDataAndMethodOptions(RequestContext $context)
129
    {
130
        $curlOptions = array();
131
132
        $requestData = $context->getData();
133
        $requestData = is_array($requestData) ? http_build_query($requestData) : $requestData;
134
135
        $url = $context->getRequestUri();
136
137
        switch ($context->getMethod()) {
138
            case RequestContext::METHOD_GET:
139
                $curlOptions[CURLOPT_HTTPGET] = 1;
140
                $url .= (strpos($url, '?') === false ? '?' : '') . $requestData;
141
                break;
142
            case RequestContext::METHOD_POST:
143
                $curlOptions[CURLOPT_POST] = 1;
144
                $curlOptions[CURLOPT_POSTFIELDS] = $requestData;
145
                break;
146
            default:
147
                $curlOptions[CURLOPT_CUSTOMREQUEST] = $context->getMethod();
148
                $curlOptions[CURLOPT_POSTFIELDS] = $requestData;
149
        }
150
151
        $curlOptions[CURLOPT_URL] = $url;
152
153
        return $curlOptions;
154
    }
155
156
    /**
157
     * @param $error
158
     * @param $errorNumber
159
     * @param $verbose
160
     *
161
     * @throws TransportException
162
     */
163
    private function handleCurlError($error, $errorNumber, $verbose): void
164
    {
165
        if (!$error) {
166
            return;
167
        }
168
169
        $errorMessage = "\ncURL transport error: $errorNumber - $error";
170
171
        $transportException = new TransportException($errorMessage);
172
173
        rewind($verbose);
174
175
        $verboseLog = stream_get_contents($verbose);
176
177
        if ($verboseLog) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $verboseLog of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
178
            $verboseLog = htmlspecialchars($verboseLog);
179
180
            $transportException->setData($verboseLog);
181
        }
182
183
        throw $transportException;
184
    }
185
186
    /**
187
     * Fill timeout options
188
     *
189
     * @param $timeout
190
     * @param $optionName
191
     * @param $optionNameMs
192
     *
193
     * @return array
194
     */
195
    private function fillTimeoutOptions($timeout, $optionName, $optionNameMs): array
196
    {
197
        $timeoutOptions = [];
198
199
        if (is_int($timeout)) {
200
            $timeoutOptions[$optionName] = $timeout;
201
        } elseif (is_float($timeout)) {
202
            $timeoutOptions[$optionNameMs] = $timeout * 1000;
203
            $timeoutOptions[CURLOPT_NOSIGNAL] = 1;
204
        }
205
206
        return $timeoutOptions;
207
    }
208
}
209