Completed
Pull Request — master (#38)
by Jan
07:04
created

ApiClient::request()   F

Complexity

Conditions 18
Paths 126

Size

Total Lines 94

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 49
CRAP Score 18.1393

Importance

Changes 0
Metric Value
dl 0
loc 94
ccs 49
cts 53
cp 0.9245
rs 3.8509
c 0
b 0
f 0
cc 18
nc 126
nop 7
crap 18.1393

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 13
			$url,
83 13
			$this->prepareData($data, $requestSignatureDataFormatter),
84 13
			null,
85 13
			$responseSignatureDataFormatter,
86 13
			$responseValidityCallback,
87 13
			$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 1
			$url,
117 1
			[],
118 1
			$this->prepareData($data, $requestSignatureDataFormatter),
119 1
			$responseSignatureDataFormatter,
120 1
			null,
121 1
			$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 1
			$url,
151 1
			[],
152 1
			$this->prepareData($data, $requestSignatureDataFormatter),
153 1
			$responseSignatureDataFormatter,
154 1
			null,
155 1
			$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 15
			$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 5
				$responseData,
236 5
				$response->getHeaders(),
237 5
				$decodedExtensions
238
			);
239
240 8
		}
241 1
		if ($response->getResponseCode()->equalsValue(ResponseCode::S303_SEE_OTHER)) {
242 1
			return new Response(
243 1
				$response->getResponseCode(),
244 1
				null,
245
				$response->getHeaders()
246
			);
247 7
248 1
		}
249 6
		if ($response->getResponseCode()->equalsValue(ResponseCode::S400_BAD_REQUEST)) {
250 1
			throw new BadRequestException($response);
251 5
		}
252 1
		if ($response->getResponseCode()->equalsValue(ResponseCode::S403_FORBIDDEN)) {
253 4
			throw new ForbiddenException($response);
254 1
		}
255 3
		if ($response->getResponseCode()->equalsValue(ResponseCode::S404_NOT_FOUND)) {
256 1
			throw new NotFoundException($response);
257 2
		}
258 1
		if ($response->getResponseCode()->equalsValue(ResponseCode::S405_METHOD_NOT_ALLOWED)) {
259
			throw new MethodNotAllowedException($response);
260
		}
261 1
		if ($response->getResponseCode()->equalsValue(ResponseCode::S429_TOO_MANY_REQUESTS)) {
262
			throw new TooManyRequestsException($response);
263
		}
264
		if ($response->getResponseCode()->equalsValue(ResponseCode::S503_SERVICE_UNAVAILABLE)) {
265
			throw new ServiceUnavailableException($response);
266
		}
267
268
		throw new InternalErrorException($response);
269
	}
270
271
	/**
272
	 * @param mixed[] $data
273
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
274 1
	 * @return Response
275
	 * @throws InvalidSignatureException
276 1
	 * @throws PrivateKeyFileException
277 1
	 * @throws SigningFailedException
278 1
	 * @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 1
			$data
286 1
		);
287
288
		$this->logRequest(HttpMethod::get(HttpMethod::GET), 'payment/response', [], [], $response, 0.0);
289
290
		return new Response(
291
			$response->getResponseCode(),
292
			$this->decodeData($data, $responseSignatureDataFormatter),
293
			$response->getHeaders()
294
		);
295
	}
296
297 15
	/**
298
	 * @param mixed[] $data
299 15
	 * @param SignatureDataFormatter $signatureDataFormatter
300 15
	 * @return mixed[]
301
	 * @throws PrivateKeyFileException
302 15
	 * @throws SigningFailedException
303
	 */
304
	private function prepareData(array $data, SignatureDataFormatter $signatureDataFormatter): array
305
	{
306
		$data['dttm'] = (new DateTimeImmutable())->format('YmdHis');
307
		$data['signature'] = $this->cryptoService->signData($data, $signatureDataFormatter);
308
309
		return $data;
310
	}
311
312
	/**
313 8
	 * @param mixed[] $responseData
314
	 * @param SignatureDataFormatter $signatureDataFormatter
315 8
	 * @return mixed[]
316 1
	 * @throws InvalidSignatureException
317
	 * @throws PublicKeyFileException
318
	 * @throws VerificationFailedException
319 7
	 */
320 7
	private function decodeData(array $responseData, SignatureDataFormatter $signatureDataFormatter): array
321
	{
322 7
		if (!array_key_exists('signature', $responseData)) {
323 1
			throw new InvalidSignatureException($responseData);
324
		}
325
326 6
		$signature = $responseData['signature'];
327
		unset($responseData['signature']);
328
329
		if (!$this->cryptoService->verifyData($responseData, $signature, $signatureDataFormatter)) {
330
			throw new InvalidSignatureException($responseData);
331
		}
332
333
		return $responseData;
334
	}
335
336
	/**
337 16
	 * @param \SlevomatCsobGateway\Api\HttpMethod $method
338
	 * @param string $url
339 16
	 * @param mixed[] $queries
340 11
	 * @param mixed[]|null $requestData
341
	 * @param \SlevomatCsobGateway\Api\Response $response
342
	 * @param float $responseTime
343 5
	 */
344
	private function logRequest(HttpMethod $method, string $url, array $queries, ?array $requestData, Response $response, float $responseTime): void
345 5
	{
346 5
		if ($this->logger === null) {
347 5
			return;
348
		}
349 5
350
		$responseData = $response->getData();
351
352
		unset($requestData['signature']);
353
		unset($queries['signature']);
354
		unset($responseData['signature']);
355
356 5
		if (isset($responseData['extensions'])) {
357 5
			foreach ($responseData['extensions'] as $key => $extensionData) {
358 5
				unset($responseData['extensions'][$key]['signature']);
359
			}
360
		}
361 5
		$context = [
362 5
			'request' => [
363 5
				'method' => $method->getValue(),
364 5
				'queries' => $queries,
365
				'data' => $requestData,
366
			],
367
			'response' => [
368 5
				'code' => $response->getResponseCode()->getValue(),
369 5
				'data' => $responseData,
370
				'headers' => $response->getHeaders(),
371
				'time' => $responseTime,
372
			],
373
		];
374
375
		$this->logger->info($url, $context);
376
	}
377
378
}
379