Passed
Push — master ( f4feef...3322ef )
by payever
10:36
created

CurlClient::execute()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 9.552
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
3
/**
4
 * PHP version 5.4 and 7
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\LoggerInterface;
23
use Psr\Log\LogLevel;
24
25
/**
26
 * This class represents Curl implementation of Client
27
 * @SuppressWarnings(PHPMD.MissingImport)
28
 */
29
class CurlClient implements HttpClientInterface, LoggerAwareInterface
30
{
31
    /** @var LoggerInterface */
32
    protected $logger;
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
     */
43
    public function __construct()
44
    {
45
        if (!extension_loaded('curl')) {
46
            throw new \RuntimeException('cURL PHP extension must be enabled in order to use this HTTP client');
47
        }
48
    }
49
50
    /**
51
     * @inheritDoc
52
     */
53
    public function setLogger(LoggerInterface $logger)
54
    {
55
        $this->logger = $logger;
56
    }
57
58
    /**
59
     * @param string $logLevel
60
     */
61
    public function setLogLevel($logLevel = LogLevel::CRITICAL)
62
    {
63
        $this->logLevel = $logLevel;
64
    }
65
66
    /**
67
     * @param string|null $logLevel
68
     */
69
    public function setLogLevelOnce($logLevel)
70
    {
71
        $this->tmpLogLevel = $logLevel;
72
    }
73
74
    /**
75
     * @param RequestInterface $request
76
     * @return Response
77
     * @throws \Exception
78
     */
79
    public function execute(RequestInterface $request)
80
    {
81
        try {
82
            return $this->executeRequest($request);
83
        } catch (\Exception $exception) {
84
            $logLevel = $this->logLevel;
85
            if (null !== $this->tmpLogLevel) {
86
                $logLevel = $this->tmpLogLevel;
87
                $this->tmpLogLevel = null;
88
            }
89
            $this->logger->log(
90
                $logLevel,
91
                sprintf(
92
                    'HTTP Request failed: %s %s',
93
                    $exception->getCode(),
94
                    $exception->getMessage()
95
                ),
96
                ['trace' => $exception->getTraceAsString()]
97
            );
98
99
            throw $exception;
100
        }
101
    }
102
103
    /**
104
     * @param RequestInterface $request
105
     *
106
     * @return Response
107
     *
108
     * @throws \RuntimeException
109
     * @throws PayeverCommunicationException
110
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
111
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
112
     * @SuppressWarnings(PHPMD.NPathComplexity)
113
     * @SuppressWarnings(PHPMD.ShortVariable)
114
     */
115
    protected function executeRequest(RequestInterface $request)
116
    {
117
        $this->logger->debug(
118
            sprintf('HTTP Request %s %s', $request->getMethod(), $request->getUrl()),
119
            ['headers' => $request->getHeaders(), 'body' => $request->toArray()]
120
        );
121
122
        if (!$request->validate()) {
123
            throw new \RuntimeException('Request entity is not valid');
124
        }
125
126
        $ch = curl_init();
127
128
        if (!is_resource($ch)) {
129
            throw new \RuntimeException('Could not get cURL resource');
130
        }
131
132
        $options = [
133
            CURLOPT_URL          => $request->getUrl(),
134
            CURLOPT_HTTPHEADER   => $request->getHeaders(),
135
            CURLOPT_HTTP_VERSION => $request->getProtocolVersion(),
136
        ];
137
138
        $customMethods = [
139
            Request::METHOD_PUT,
140
            Request::METHOD_PATCH,
141
            Request::METHOD_DELETE,
142
        ];
143
144
        if ($request->getMethod() === Request::METHOD_POST) {
145
            $options[CURLOPT_POST] = true;
146
            $options[CURLOPT_POSTFIELDS] = $request->toArray();
147
        } elseif ($request->getMethod() === Request::METHOD_GET && $request->toArray()) {
148
            $paramChar = strpos('?', $request->getUrl()) === false ? '?' : '&';
149
            $options[CURLOPT_URL] = $request->getUrl() . $paramChar . http_build_query($request->toArray());
150
        } elseif (in_array($request->getMethod(), $customMethods)) {
151
            $options[CURLOPT_CUSTOMREQUEST] = $request->getMethod();
152
            $options[CURLOPT_POSTFIELDS] = $request->toArray();
153
        }
154
155
        if (
156
            isset($options[CURLOPT_POSTFIELDS]) && is_array($options[CURLOPT_POSTFIELDS])
157
            && $request->getHeader('Content-Type') == 'application/x-www-form-urlencoded'
158
        ) {
159
            $options[CURLOPT_POSTFIELDS] = http_build_query($options[CURLOPT_POSTFIELDS]);
160
        } elseif (
161
            isset($options[CURLOPT_POSTFIELDS]) && is_array($options[CURLOPT_POSTFIELDS])
162
            && $request->getHeader('Content-Type') == 'application/json'
163
        ) {
164
            $options[CURLOPT_POSTFIELDS] = json_encode($options[CURLOPT_POSTFIELDS]);
165
        }
166
167
        curl_setopt_array($ch, $this->getRequestOptions($options));
168
169
        $result       = curl_exec($ch);
170
        $httpCode     = curl_getinfo($ch, CURLINFO_HTTP_CODE);
171
        $errorMessage = curl_error($ch);
172
        $errorNumber  = curl_errno($ch);
173
174
        curl_close($ch);
175
176
        $this->logger->debug(
177
            sprintf('HTTP Response %s %s', $request->getMethod(), $request->getUrl()),
178
            ['httpCode' => $httpCode, 'body' => $result, 'curlError' => $errorMessage]
179
        );
180
181
        if ($errorNumber !== 0) {
182
            throw new PayeverCommunicationException($errorMessage, $errorNumber);
183
        }
184
185
        if ($httpCode >= 400) {
186
            $message = $result;
187
            $data = json_decode($result, true);
188
189
            if (isset($data['error_description'])) {
190
                $message = $data['error_description'];
191
            } elseif (isset($data['message'])) {
192
                $message = $data['message'];
193
            }
194
195
            if (isset($data['error'])) {
196
                $message = sprintf('%s: %s', $data['error'], $message ?: 'Unknown');
197
            }
198
199
            throw new PayeverCommunicationException($message, $httpCode);
200
        }
201
202
        $response = new Response();
203
204
        $response
205
            ->setRequestEntity($request->getRequestEntity())
206
            ->setResponseEntity($request->getResponseEntity())
207
            ->load($result);
208
209
        if ($response->isFailed()) {
210
            throw new PayeverCommunicationException(
211
                $response->getResponseEntity()->getErrorDescription()
212
            );
213
        }
214
215
        return $response;
216
    }
217
218
    /**
219
     * Returns cURL request params array
220
     *
221
     * @param array $override
222
     *
223
     * @return array
224
     */
225
    protected function getRequestOptions($override)
226
    {
227
        $default = [
228
            CURLOPT_HEADER         => 0,
229
            CURLOPT_RETURNTRANSFER => true,
230
            CURLOPT_SSL_VERIFYPEER => false,
231
            CURLOPT_SSL_VERIFYHOST => 0,
232
            CURLOPT_TIMEOUT        => 30,
233
            CURLOPT_CONNECTTIMEOUT => 15,
234
            CURLOPT_HTTP_VERSION   => 1.1,
235
        ];
236
237
        return $override + $default;
238
    }
239
}
240