ApiClient::request()   D
last analyzed

Complexity

Conditions 18
Paths 126

Size

Total Lines 93
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 46
CRAP Score 18.1658

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 18
eloc 52
c 2
b 0
f 0
nc 126
nop 7
dl 0
loc 93
rs 4.65
ccs 46
cts 50
cp 0.92
crap 18.1658

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 declare(strict_types = 1);
2
3
namespace SlevomatCsobGateway\Api;
4
5
use Closure;
6
use DateTimeImmutable;
7
use InvalidArgumentException;
8
use Psr\Log\LoggerInterface;
9
use SlevomatCsobGateway\Call\ResponseExtensionHandler;
10
use SlevomatCsobGateway\Crypto\CryptoService;
11
use SlevomatCsobGateway\Crypto\PrivateKeyFileException;
12
use SlevomatCsobGateway\Crypto\PublicKeyFileException;
13
use SlevomatCsobGateway\Crypto\SignatureDataFormatter;
14
use SlevomatCsobGateway\Crypto\SigningFailedException;
15
use SlevomatCsobGateway\Crypto\VerificationFailedException;
16
use function array_key_exists;
17
use function json_encode;
18
use function microtime;
19
use function str_replace;
20
use function strpos;
21
use function substr;
22
use function urlencode;
23
24
class ApiClient
25
{
26
27
	/** @var ApiClientDriver */
28
	private $driver;
29
30
	/** @var CryptoService */
31
	private $cryptoService;
32
33
	/** @var LoggerInterface|null */
34
	private $logger;
35
36
	/** @var string|null */
37
	private $apiUrl;
38
39 16
	public function __construct(
40
		ApiClientDriver $driver,
41
		CryptoService $cryptoService,
42
		string $apiUrl
43
	)
44
	{
45 16
		$this->driver = $driver;
46 16
		$this->cryptoService = $cryptoService;
47 16
		$this->apiUrl = $apiUrl;
48 16
	}
49
50 5
	public function setLogger(?LoggerInterface $logger): void
51
	{
52 5
		$this->logger = $logger;
53 5
	}
54
55
	/**
56
	 * @param string $url
57
	 * @param mixed[] $data
58
	 * @param SignatureDataFormatter $requestSignatureDataFormatter
59
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
60
	 * @param Closure|null $responseValidityCallback
61
	 * @param ResponseExtensionHandler[] $extensions
62
	 * @return Response
63
	 *
64
	 * @throws PrivateKeyFileException
65
	 * @throws SigningFailedException
66
	 * @throws PublicKeyFileException
67
	 * @throws VerificationFailedException
68
	 * @throws RequestException
69
	 * @throws ApiClientDriverException
70
	 * @throws InvalidSignatureException
71
	 */
72 13
	public function get(
73
		string $url,
74
		array $data,
75
		SignatureDataFormatter $requestSignatureDataFormatter,
76
		SignatureDataFormatter $responseSignatureDataFormatter,
77
		?Closure $responseValidityCallback = null,
78
		array $extensions = []
79
	): Response
80
	{
81 13
		return $this->request(
82 13
			HttpMethod::get(HttpMethod::GET),
83
			$url,
84 13
			$this->prepareData($data, $requestSignatureDataFormatter),
85 13
			null,
86
			$responseSignatureDataFormatter,
87
			$responseValidityCallback,
88
			$extensions
89
		);
90
	}
91
92
	/**
93
	 * @param string $url
94
	 * @param mixed[] $data
95
	 * @param SignatureDataFormatter $requestSignatureDataFormatter
96
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
97
	 * @param ResponseExtensionHandler[] $extensions
98
	 * @return Response
99
	 *
100
	 * @throws PrivateKeyFileException
101
	 * @throws SigningFailedException
102
	 * @throws PublicKeyFileException
103
	 * @throws VerificationFailedException
104
	 * @throws RequestException
105
	 * @throws ApiClientDriverException
106
	 * @throws InvalidSignatureException
107
	 */
108 1
	public function post(
109
		string $url,
110
		array $data,
111
		SignatureDataFormatter $requestSignatureDataFormatter,
112
		SignatureDataFormatter $responseSignatureDataFormatter,
113
		array $extensions = []
114
	): Response
115
	{
116 1
		return $this->request(
117 1
			HttpMethod::get(HttpMethod::POST),
118
			$url,
119 1
			[],
120 1
			$this->prepareData($data, $requestSignatureDataFormatter),
121
			$responseSignatureDataFormatter,
122 1
			null,
123
			$extensions
124
		);
125
	}
126
127
	/**
128
	 * @param string $url
129
	 * @param mixed[] $data
130
	 * @param SignatureDataFormatter $requestSignatureDataFormatter
131
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
132
	 * @param ResponseExtensionHandler[] $extensions
133
	 * @return Response
134
	 *
135
	 * @throws PrivateKeyFileException
136
	 * @throws SigningFailedException
137
	 * @throws PublicKeyFileException
138
	 * @throws VerificationFailedException
139
	 * @throws RequestException
140
	 * @throws ApiClientDriverException
141
	 * @throws InvalidSignatureException
142
	 */
143 1
	public function put(
144
		string $url,
145
		array $data,
146
		SignatureDataFormatter $requestSignatureDataFormatter,
147
		SignatureDataFormatter $responseSignatureDataFormatter,
148
		array $extensions = []
149
	): Response
150
	{
151 1
		return $this->request(
152 1
			HttpMethod::get(HttpMethod::PUT),
153
			$url,
154 1
			[],
155 1
			$this->prepareData($data, $requestSignatureDataFormatter),
156
			$responseSignatureDataFormatter,
157 1
			null,
158
			$extensions
159
		);
160
	}
161
162
	/**
163
	 * @param HttpMethod $method
164
	 * @param string $url
165
	 * @param mixed[] $queries
166
	 * @param mixed[]|null $data
167
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
168
	 * @param Closure|null $responseValidityCallback
169
	 * @param ResponseExtensionHandler[] $extensions
170
	 * @return Response
171
	 *
172
	 * @throws PrivateKeyFileException
173
	 * @throws SigningFailedException
174
	 * @throws PublicKeyFileException
175
	 * @throws VerificationFailedException
176
	 * @throws RequestException
177
	 * @throws ApiClientDriverException
178
	 * @throws InvalidSignatureException
179
	 */
180 15
	private function request(
181
		HttpMethod $method,
182
		string $url,
183
		array $queries,
184
		?array $data,
185
		SignatureDataFormatter $responseSignatureDataFormatter,
186
		?Closure $responseValidityCallback,
187
		array $extensions = []
188
	): Response
189
	{
190 15
		$urlFirstQueryPosition = strpos($url, '{');
191 15
		$endpointName = $urlFirstQueryPosition !== false ? substr($url, 0, $urlFirstQueryPosition - 1) : $url;
192 15
		$originalQueries = $queries;
193
194 15
		foreach ($queries as $key => $value) {
195 13
			if (strpos($url, '{' . $key . '}') === false) {
196
				continue;
197
			}
198
199 13
			$url = str_replace('{' . $key . '}', urlencode((string) $value), $url);
200 13
			unset($queries[$key]);
201
		}
202
203 15
		if ($queries !== []) {
204
			throw new InvalidArgumentException('Arguments are missing URL placeholders: ' . json_encode($queries));
205
		}
206
207 15
		$requestStartTime = microtime(true);
208
209 15
		$response = $this->driver->request(
210 15
			$method,
211 15
			$this->apiUrl . '/' . $url,
212
			$data
213
		);
214
215 15
		$this->logRequest($method, $endpointName, $originalQueries, $data, $response, microtime(true) - $requestStartTime);
216
217 15
		if ($responseValidityCallback !== null) {
218
			$responseValidityCallback($response);
219
		}
220
221 15
		if ($response->getResponseCode()->equalsValue(ResponseCode::S200_OK)) {
222 7
			$decodedExtensions = [];
223 7
			if ($extensions !== [] && array_key_exists('extensions', (array) $response->getData())) {
224 1
				foreach ($response->getData()['extensions'] as $extensionData) {
225 1
					$name = $extensionData['extension'];
226 1
					if (!isset($extensions[$name])) {
227
						continue;
228
					}
229
230 1
					$handler = $extensions[$name];
231 1
					$decodedExtensions[$name] = $handler->createResponse($this->decodeData($extensionData, $handler->getSignatureDataFormatter()));
232
				}
233
			}
234 7
			$responseData = $this->decodeData($response->getData() ?? [], $responseSignatureDataFormatter);
235 5
			unset($responseData['extensions']);
236
237 5
			return new Response(
238 5
				$response->getResponseCode(),
239
				$responseData,
240 5
				$response->getHeaders(),
241
				$decodedExtensions
242
			);
243
244
		}
245 8
		if ($response->getResponseCode()->equalsValue(ResponseCode::S303_SEE_OTHER)) {
246 1
			return new Response(
247 1
				$response->getResponseCode(),
248 1
				null,
249 1
				$response->getHeaders()
250
			);
251
252
		}
253 7
		if ($response->getResponseCode()->equalsValue(ResponseCode::S400_BAD_REQUEST)) {
254 1
			throw new BadRequestException($response);
255
		}
256 6
		if ($response->getResponseCode()->equalsValue(ResponseCode::S403_FORBIDDEN)) {
257 1
			throw new ForbiddenException($response);
258
		}
259 5
		if ($response->getResponseCode()->equalsValue(ResponseCode::S404_NOT_FOUND)) {
260 1
			throw new NotFoundException($response);
261
		}
262 4
		if ($response->getResponseCode()->equalsValue(ResponseCode::S405_METHOD_NOT_ALLOWED)) {
263 1
			throw new MethodNotAllowedException($response);
264
		}
265 3
		if ($response->getResponseCode()->equalsValue(ResponseCode::S429_TOO_MANY_REQUESTS)) {
266 1
			throw new TooManyRequestsException($response);
267
		}
268 2
		if ($response->getResponseCode()->equalsValue(ResponseCode::S503_SERVICE_UNAVAILABLE)) {
269 1
			throw new ServiceUnavailableException($response);
270
		}
271
272 1
		throw new InternalErrorException($response);
273
	}
274
275
	/**
276
	 * @param mixed[] $data
277
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
278
	 * @return Response
279
	 *
280
	 * @throws InvalidSignatureException
281
	 * @throws PrivateKeyFileException
282
	 * @throws SigningFailedException
283
	 * @throws PublicKeyFileException
284
	 * @throws VerificationFailedException
285
	 */
286 1
	public function createResponseByData(array $data, SignatureDataFormatter $responseSignatureDataFormatter): Response
287
	{
288 1
		$response = new Response(
289 1
			ResponseCode::get(ResponseCode::S200_OK),
290
			$data
291
		);
292
293 1
		$this->logRequest(HttpMethod::get(HttpMethod::GET), 'payment/response', [], [], $response, 0.0);
294
295 1
		return new Response(
296 1
			$response->getResponseCode(),
297 1
			$this->decodeData($data, $responseSignatureDataFormatter),
298 1
			$response->getHeaders()
299
		);
300
	}
301
302
	/**
303
	 * @param mixed[] $data
304
	 * @param SignatureDataFormatter $signatureDataFormatter
305
	 * @return mixed[]
306
	 *
307
	 * @throws PrivateKeyFileException
308
	 * @throws SigningFailedException
309
	 */
310 15
	private function prepareData(array $data, SignatureDataFormatter $signatureDataFormatter): array
311
	{
312 15
		$data['dttm'] = (new DateTimeImmutable())->format('YmdHis');
313 15
		$data['signature'] = $this->cryptoService->signData($data, $signatureDataFormatter);
314
315 15
		return $data;
316
	}
317
318
	/**
319
	 * @param mixed[] $responseData
320
	 * @param SignatureDataFormatter $signatureDataFormatter
321
	 * @return mixed[]
322
	 *
323
	 * @throws InvalidSignatureException
324
	 * @throws PublicKeyFileException
325
	 * @throws VerificationFailedException
326
	 */
327 8
	private function decodeData(array $responseData, SignatureDataFormatter $signatureDataFormatter): array
328
	{
329 8
		if (!array_key_exists('signature', $responseData)) {
330 1
			throw new InvalidSignatureException($responseData);
331
		}
332
333 7
		$signature = $responseData['signature'];
334 7
		unset($responseData['signature']);
335
336 7
		if (!$this->cryptoService->verifyData($responseData, $signature, $signatureDataFormatter)) {
337 1
			throw new InvalidSignatureException($responseData);
338
		}
339
340 6
		return $responseData;
341
	}
342
343
	/**
344
	 * @param HttpMethod $method
345
	 * @param string $url
346
	 * @param mixed[] $queries
347
	 * @param mixed[]|null $requestData
348
	 * @param Response $response
349
	 * @param float $responseTime
350
	 */
351 16
	private function logRequest(HttpMethod $method, string $url, array $queries, ?array $requestData, Response $response, float $responseTime): void
352
	{
353 16
		if ($this->logger === null) {
354 11
			return;
355
		}
356
357 5
		$responseData = $response->getData();
358
359 5
		unset($requestData['signature']);
360 5
		unset($queries['signature']);
361 5
		unset($responseData['signature']);
362
363 5
		if (isset($responseData['extensions'])) {
364
			foreach ($responseData['extensions'] as $key => $extensionData) {
365
				unset($responseData['extensions'][$key]['signature']);
366
			}
367
		}
368
		$context = [
369
			'request' => [
370 5
				'method' => $method->getValue(),
371 5
				'queries' => $queries,
372 5
				'data' => $requestData,
373
			],
374
			'response' => [
375 5
				'code' => $response->getResponseCode()->getValue(),
376 5
				'data' => $responseData,
377 5
				'headers' => $response->getHeaders(),
378 5
				'time' => $responseTime,
379
			],
380
		];
381
382 5
		$this->logger->info($url, $context);
383 5
	}
384
385
}
386