CurlClient::executeRequest()   F
last analyzed

Complexity

Conditions 22
Paths 137

Size

Total Lines 107
Code Lines 68

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 68
c 2
b 0
f 0
dl 0
loc 107
rs 3.8583
cc 22
nc 137
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * PHP version 5.4 and 8
5
 *
6
 * @category  Http
7
 * @package   Payever\Core
8
 * @author    payever GmbH <[email protected]>
9
 * @copyright 2017-2021 payever GmbH
10
 * @license   MIT <https://opensource.org/licenses/MIT>
11
 * @link      https://docs.payever.org/shopsystems/api/getting-started
12
 */
13
14
namespace Payever\ExternalIntegration\Core\Http\Client;
15
16
use Payever\ExternalIntegration\Core\Base\HttpClientInterface;
17
use Payever\ExternalIntegration\Core\Base\RequestInterface;
18
use Payever\ExternalIntegration\Core\Exception\PayeverCommunicationException;
19
use Payever\ExternalIntegration\Core\Http\Request;
20
use Payever\ExternalIntegration\Core\Http\Response;
21
use Psr\Log\LoggerAwareInterface;
22
use Psr\Log\LoggerAwareTrait;
23
use Psr\Log\LoggerInterface;
24
use Psr\Log\LogLevel;
25
26
/**
27
 * This class represents Curl implementation of Client
28
 * @SuppressWarnings(PHPMD.MissingImport)
29
 */
30
class CurlClient implements HttpClientInterface, LoggerAwareInterface
31
{
32
    use LoggerAwareTrait;
33
34
    /** @var string */
35
    protected $logLevel = LogLevel::CRITICAL;
36
37
    /** @var string|null */
38
    protected $tmpLogLevel;
39
40
    /**
41
     * @throws \RuntimeException when cURL extension is not enabled
42
     * @codeCoverageIgnore
43
     */
44
    public function __construct()
45
    {
46
        if (!extension_loaded('curl')) {
47
            throw new \RuntimeException('cURL PHP extension must be enabled in order to use this HTTP client');
48
        }
49
    }
50
51
    /**
52
     * @param string $logLevel
53
     */
54
    public function setLogLevel($logLevel = LogLevel::CRITICAL)
55
    {
56
        $this->logLevel = $logLevel;
57
    }
58
59
    /**
60
     * @param string|null $logLevel
61
     */
62
    public function setLogLevelOnce($logLevel)
63
    {
64
        $this->tmpLogLevel = $logLevel;
65
    }
66
67
    /**
68
     * @param RequestInterface $request
69
     * @return Response
70
     * @throws \Exception
71
     */
72
    public function execute(RequestInterface $request)
73
    {
74
        try {
75
            return $this->executeRequest($request);
76
        } catch (\Exception $exception) {
77
            $logLevel = $this->logLevel;
78
            if (null !== $this->tmpLogLevel) {
79
                $logLevel = $this->tmpLogLevel;
80
                $this->tmpLogLevel = null;
81
            }
82
            $this->logger->log(
0 ignored issues
show
Bug introduced by
The method log() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

82
            $this->logger->/** @scrutinizer ignore-call */ 
83
                           log(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
83
                $logLevel,
84
                sprintf(
85
                    'HTTP Request failed: %s %s',
86
                    $exception->getCode(),
87
                    $exception->getMessage()
88
                ),
89
                ['trace' => $exception->getTraceAsString()]
90
            );
91
92
            throw $exception;
93
        }
94
    }
95
96
    /**
97
     * @param RequestInterface $request
98
     *
99
     * @return Response
100
     *
101
     * @throws \RuntimeException
102
     * @throws PayeverCommunicationException
103
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
104
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
105
     * @SuppressWarnings(PHPMD.NPathComplexity)
106
     * @SuppressWarnings(PHPMD.ShortVariable)
107
     */
108
    protected function executeRequest(RequestInterface $request)
109
    {
110
        $this->logger->debug(
111
            sprintf('HTTP Request %s %s', $request->getMethod(), $request->getUrl()),
112
            ['headers' => $request->getHeaders(), 'body' => $request->toArray()]
113
        );
114
115
        if (!$request->validate()) {
116
            throw new \RuntimeException('Request entity is not valid');
117
        }
118
119
        $ch = curl_init();
120
121
        if ($ch === false) {
122
            throw new \RuntimeException('Could not get cURL resource');
123
        }
124
125
        $options = [
126
            CURLOPT_URL          => $request->getUrl(),
127
            CURLOPT_HTTPHEADER   => $request->getHeaders(),
128
            CURLOPT_HTTP_VERSION => $request->getProtocolVersion(),
129
        ];
130
131
        $customMethods = [
132
            Request::METHOD_PUT,
133
            Request::METHOD_PATCH,
134
            Request::METHOD_DELETE,
135
        ];
136
137
        if ($request->getMethod() === Request::METHOD_POST) {
138
            $options[CURLOPT_POST] = true;
139
            $options[CURLOPT_POSTFIELDS] = $request->toArray();
140
        } elseif ($request->getMethod() === Request::METHOD_GET && $request->toArray()) {
141
            $paramChar = strpos('?', $request->getUrl()) === false ? '?' : '&';
142
            $options[CURLOPT_URL] = $request->getUrl() . $paramChar . http_build_query($request->toArray());
143
        } elseif (in_array($request->getMethod(), $customMethods)) {
144
            $options[CURLOPT_CUSTOMREQUEST] = $request->getMethod();
145
            $options[CURLOPT_POSTFIELDS] = $request->toArray();
146
        }
147
148
        if (
149
            isset($options[CURLOPT_POSTFIELDS]) && is_array($options[CURLOPT_POSTFIELDS])
150
            && $request->getHeader('Content-Type') == 'application/x-www-form-urlencoded'
151
        ) {
152
            $options[CURLOPT_POSTFIELDS] = http_build_query($options[CURLOPT_POSTFIELDS]);
0 ignored issues
show
Bug introduced by
It seems like $options[Payever\Externa...ent\CURLOPT_POSTFIELDS] can also be of type string; however, parameter $data of http_build_query() does only seem to accept array|object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

152
            $options[CURLOPT_POSTFIELDS] = http_build_query(/** @scrutinizer ignore-type */ $options[CURLOPT_POSTFIELDS]);
Loading history...
153
        } elseif (
154
            isset($options[CURLOPT_POSTFIELDS]) && is_array($options[CURLOPT_POSTFIELDS])
155
            && $request->getHeader('Content-Type') == 'application/json'
156
        ) {
157
            $options[CURLOPT_POSTFIELDS] = json_encode($options[CURLOPT_POSTFIELDS]);
158
        }
159
160
        curl_setopt_array($ch, $this->getRequestOptions($options));
161
162
        $result       = curl_exec($ch);
163
        $httpCode     = curl_getinfo($ch, CURLINFO_HTTP_CODE);
164
        $errorMessage = curl_error($ch);
165
        $errorNumber  = curl_errno($ch);
166
167
        curl_close($ch);
168
169
        $this->logger->debug(
170
            sprintf('HTTP Response %s %s', $request->getMethod(), $request->getUrl()),
171
            ['httpCode' => $httpCode, 'body' => $result, 'curlError' => $errorMessage]
172
        );
173
174
        if ($errorNumber !== 0) {
175
            throw new PayeverCommunicationException($errorMessage, $errorNumber);
176
        }
177
178
        if ($httpCode >= 400) {
179
            $message = $result;
180
            $data = json_decode($result, true);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type true; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

180
            $data = json_decode(/** @scrutinizer ignore-type */ $result, true);
Loading history...
181
182
            if (isset($data['error_description'])) {
183
                $message = $data['error_description'];
184
            } elseif (isset($data['message'])) {
185
                $message = $data['message'];
186
            }
187
188
            if (isset($data['error'])) {
189
                $message = sprintf(
190
                    '%s: %s',
191
                    $data['error'],
192
                    $message
193
                        ? (is_array($message) ? json_encode($message) : $message)
194
                        : 'Unknown'
195
                );
196
            }
197
198
            throw new PayeverCommunicationException($message, $httpCode);
199
        }
200
201
        $response = new Response();
202
203
        $response
204
            ->setRequestEntity($request->getRequestEntity())
205
            ->setResponseEntity($request->getResponseEntity())
206
            ->load($result);
207
208
        if ($response->isFailed()) {
209
            throw new PayeverCommunicationException(
210
                $response->getResponseEntity()->getErrorDescription()
211
            );
212
        }
213
214
        return $response;
215
    }
216
217
    /**
218
     * @param $fileUrl
219
     * @param $savePath
220
     * @throws \Exception
221
     */
222
    public function download($fileUrl, $savePath)
223
    {
224
        try {
225
            $this->downloadRequest($fileUrl, $savePath);
226
        } catch (\Exception $exception) {
227
            $logLevel = $this->logLevel;
228
            if (null !== $this->tmpLogLevel) {
229
                $logLevel = $this->tmpLogLevel;
230
                $this->tmpLogLevel = null;
231
            }
232
            $this->logger->log(
233
                $logLevel,
234
                sprintf(
235
                    'HTTP Request failed: %s %s',
236
                    $exception->getCode(),
237
                    $exception->getMessage()
238
                ),
239
                ['trace' => $exception->getTraceAsString()]
240
            );
241
242
            throw $exception;
243
        }
244
    }
245
246
    /**
247
     * @param $fileUrl
248
     * @param $savePath
249
     *
250
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
251
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
252
     * @SuppressWarnings(PHPMD.NPathComplexity)
253
     * @SuppressWarnings(PHPMD.ShortVariable)
254
     */
255
    protected function downloadRequest($fileUrl, $savePath)
256
    {
257
        $this->logger->debug(
258
            sprintf('HTTP download Request %s %s', $fileUrl, $savePath)
259
        );
260
261
        $filePointer = fopen($savePath, 'w+');
262
        $ch = curl_init($fileUrl);
263
264
        if ($ch === false) {
265
            throw new \RuntimeException('Could not get cURL resource');
266
        }
267
268
        $options = $this->getRequestOptions();
269
        $options[CURLOPT_TIMEOUT] = 100;
270
        $options[CURLOPT_CONNECTTIMEOUT] = 300;
271
        $options[CURLOPT_FILE] = $filePointer;
272
273
        curl_setopt_array($ch, $options);
274
275
        curl_exec($ch);
276
        $httpCode     = curl_getinfo($ch, CURLINFO_HTTP_CODE);
277
        $errorMessage = curl_error($ch);
278
        $errorNumber  = curl_errno($ch);
279
280
        curl_close($ch);
281
        fclose($filePointer);
282
283
        $this->logger->debug(
284
            sprintf('HTTP Download Response %s', $savePath),
285
            ['httpCode' => $httpCode, 'curlError' => $errorMessage]
286
        );
287
288
        if ($errorNumber !== 0) {
289
            throw new PayeverCommunicationException($errorMessage, $errorNumber);
290
        }
291
292
        if ($httpCode >= 400) {
293
            throw new PayeverCommunicationException('Could not download the file', $httpCode);
294
        }
295
    }
296
297
    /**
298
     * Returns cURL request params array
299
     *
300
     * @param array $override
301
     *
302
     * @return array
303
     */
304
    protected function getRequestOptions($override = [])
305
    {
306
        $default = [
307
            CURLOPT_HEADER         => 0,
308
            CURLOPT_RETURNTRANSFER => true,
309
            CURLOPT_SSL_VERIFYPEER => true,
310
            CURLOPT_SSL_VERIFYHOST => 2,
311
            CURLOPT_TIMEOUT        => 30,
312
            CURLOPT_CONNECTTIMEOUT => 15,
313
            CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_1_1,
314
        ];
315
316
        return $override + $default;
317
    }
318
}
319