Completed
Push — master ( 955097...f4414e )
by Jan
08:34
created

ApiClient::request()   D

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