Completed
Push — master ( 3c4bc3...16a3d2 )
by
unknown
04:19
created

ApiClient   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 360
Duplicated Lines 15.28 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 96.77%

Importance

Changes 0
Metric Value
wmc 33
lcom 1
cbo 12
dl 55
loc 360
ccs 120
cts 124
cp 0.9677
rs 9.3999
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A setLogger() 0 4 1
A __construct() 0 10 1
A prepareData() 0 7 1
A decodeData() 0 15 3
A get() 19 19 1
A post() 18 18 1
A put() 18 18 1
D request() 0 86 19
A createResponseByData() 0 15 1
B logRequest() 0 32 4

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php declare(strict_types = 1);
2
3
namespace SlevomatCsobGateway\Api;
4
5
use DateTimeImmutable;
6
use Psr\Log\LoggerInterface;
7
use SlevomatCsobGateway\Call\ResponseExtensionHandler;
8
use SlevomatCsobGateway\Crypto\CryptoService;
9
use SlevomatCsobGateway\Crypto\PrivateKeyFileException;
10
use SlevomatCsobGateway\Crypto\PublicKeyFileException;
11
use SlevomatCsobGateway\Crypto\SignatureDataFormatter;
12
use SlevomatCsobGateway\Crypto\SigningFailedException;
13
use SlevomatCsobGateway\Crypto\VerificationFailedException;
14
15
class ApiClient
16
{
17
18
	/**
19
	 * @var ApiClientDriver
20
	 */
21
	private $driver;
22
23
	/**
24
	 * @var CryptoService
25
	 */
26
	private $cryptoService;
27
28
	/**
29
	 * @var LoggerInterface|null
30
	 */
31
	private $logger;
32
33
	/**
34
	 * @var string|null
35
	 */
36
	private $apiUrl;
37
38 16
	public function __construct(
39
		ApiClientDriver $driver,
40
		CryptoService $cryptoService,
41
		string $apiUrl
42
	)
43
	{
44 16
		$this->driver = $driver;
45 16
		$this->cryptoService = $cryptoService;
46 16
		$this->apiUrl = $apiUrl;
47 16
	}
48
49 5
	public function setLogger(LoggerInterface $logger = null)
50
	{
51 5
		$this->logger = $logger;
52 5
	}
53
54
	/**
55
	 * @param string $url
56
	 * @param mixed[] $data
57
	 * @param SignatureDataFormatter $requestSignatureDataFormatter
58
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
59
	 * @param \Closure|null $responseValidityCallback
60
	 * @param ResponseExtensionHandler[] $extensions
61
	 * @return Response
62
	 *
63
	 * @throws PrivateKeyFileException
64
	 * @throws SigningFailedException
65
	 * @throws PublicKeyFileException
66
	 * @throws VerificationFailedException
67
	 * @throws RequestException
68
	 * @throws ApiClientDriverException
69
	 * @throws InvalidSignatureException
70
	 */
71 13 View Code Duplication
	public function get(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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
	 *
99
	 * @throws PrivateKeyFileException
100
	 * @throws SigningFailedException
101
	 * @throws PublicKeyFileException
102
	 * @throws VerificationFailedException
103
	 * @throws RequestException
104
	 * @throws ApiClientDriverException
105
	 * @throws InvalidSignatureException
106
	 */
107 1 View Code Duplication
	public function post(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
108
		string $url,
109
		array $data = [],
110
		SignatureDataFormatter $requestSignatureDataFormatter,
111
		SignatureDataFormatter $responseSignatureDataFormatter,
112
		array $extensions = []
113
	): Response
114
	{
115 1
		return $this->request(
116 1
			HttpMethod::get(HttpMethod::POST),
117 1
			$url,
118 1
			[],
119 1
			$this->prepareData($data, $requestSignatureDataFormatter),
120 1
			$responseSignatureDataFormatter,
121 1
			null,
122 1
			$extensions
123
		);
124
	}
125
126
	/**
127
	 * @param string $url
128
	 * @param mixed[] $data
129
	 * @param SignatureDataFormatter $requestSignatureDataFormatter
130
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
131
	 * @param ResponseExtensionHandler[] $extensions
132
	 * @return Response
133
	 *
134
	 * @throws PrivateKeyFileException
135
	 * @throws SigningFailedException
136
	 * @throws PublicKeyFileException
137
	 * @throws VerificationFailedException
138
	 * @throws RequestException
139
	 * @throws ApiClientDriverException
140
	 * @throws InvalidSignatureException
141
	 */
142 1 View Code Duplication
	public function put(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
143
		string $url,
144
		array $data = [],
145
		SignatureDataFormatter $requestSignatureDataFormatter,
146
		SignatureDataFormatter $responseSignatureDataFormatter,
147
		array $extensions = []
148
	): Response
149
	{
150 1
		return $this->request(
151 1
			HttpMethod::get(HttpMethod::PUT),
152 1
			$url,
153 1
			[],
154 1
			$this->prepareData($data, $requestSignatureDataFormatter),
155 1
			$responseSignatureDataFormatter,
156 1
			null,
157 1
			$extensions
158
		);
159
	}
160
161
	/**
162
	 * @param HttpMethod $method
163
	 * @param string $url
164
	 * @param mixed[] $queries
165
	 * @param mixed[]|null $data
166
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
167
	 * @param \Closure|null $responseValidityCallback
168
	 * @param ResponseExtensionHandler[] $extensions
169
	 * @return Response
170
	 *
171
	 * @throws PrivateKeyFileException
172
	 * @throws SigningFailedException
173
	 * @throws PublicKeyFileException
174
	 * @throws VerificationFailedException
175
	 * @throws RequestException
176
	 * @throws ApiClientDriverException
177
	 * @throws InvalidSignatureException
178
	 */
179 15
	public function request(
180
		HttpMethod $method,
181
		string $url,
182
		array $queries = [],
183
		array $data = null,
184
		SignatureDataFormatter $responseSignatureDataFormatter,
185
		\Closure $responseValidityCallback = null,
186
		array $extensions = []
187
	): Response
188
	{
189 15
		$urlFirstQueryPosition = strpos($url, '{');
190 15
		$endpointName = ($urlFirstQueryPosition !== false ? substr($url, 0, $urlFirstQueryPosition - 1) : $url);
191 15
		$originalQueries = $queries;
192
193 15
		foreach ($queries as $key => $value) {
194 13
			if (strpos($url, '{' . $key . '}') !== false) {
195 13
				$url = str_replace('{' . $key . '}', urlencode((string) $value), $url);
196 13
				unset($queries[$key]);
197
			}
198
		}
199
200 15
		if ($queries !== []) {
201
			throw new \InvalidArgumentException('Arguments are missing URL placeholders: ' . json_encode($queries));
202
		}
203
204 15
		$response = $this->driver->request(
205 15
			$method,
206 15
			$this->apiUrl . '/' . $url,
207 15
			$data
208
		);
209
210 15
		$this->logRequest($method, $endpointName, $originalQueries, $data, $response);
211
212 15
		if ($responseValidityCallback !== null) {
213
			$responseValidityCallback($response);
214
		}
215
216 15
		if ($response->getResponseCode()->equalsValue(ResponseCode::S200_OK)) {
217 7
			$decodedExtensions = [];
218 7
			if ($extensions !== [] && array_key_exists('extensions', $response->getData())) {
219 1
				foreach ($response->getData()['extensions'] as $extensionData) {
220 1
					$name = $extensionData['extension'];
221 1
					if (isset($extensions[$name])) {
222 1
						$handler = $extensions[$name];
223 1
						$decodedExtensions[$name] = $handler->createResponse($this->decodeData($extensionData, $handler->getSignatureDataFormatter()));
224
					}
225
				}
226
			}
227 7
			$responseData = $this->decodeData($response->getData() ?: [], $responseSignatureDataFormatter);
228 5
			unset($responseData['extensions']);
229
230 5
			return new Response(
231 5
				$response->getResponseCode(),
232 5
				$responseData,
233 5
				$response->getHeaders(),
234 5
				$decodedExtensions
235
			);
236
237 8
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S303_SEE_OTHER)) {
238 1
			return new Response(
239 1
				$response->getResponseCode(),
240 1
				null,
241 1
				$response->getHeaders()
242
			);
243
244 7
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S400_BAD_REQUEST)) {
245 1
			throw new BadRequestException($response);
246
247 6
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S403_FORBIDDEN)) {
248 1
			throw new ForbiddenException($response);
249
250 5
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S404_NOT_FOUND)) {
251 1
			throw new NotFoundException($response);
252
253 4
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S405_METHOD_NOT_ALLOWED)) {
254 1
			throw new MethodNotAllowedException($response);
255
256 3
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S429_TOO_MANY_REQUESTS)) {
257 1
			throw new TooManyRequestsException($response);
258
259 2
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S503_SERVICE_UNAVAILABLE)) {
260 1
			throw new ServiceUnavailableException($response);
261
		}
262
263 1
		throw new InternalErrorException($response);
264
	}
265
266
	/**
267
	 * @param mixed[] $data
268
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
269
	 * @return Response
270
	 *
271
	 * @throws InvalidSignatureException
272
	 * @throws PrivateKeyFileException
273
	 * @throws SigningFailedException
274
	 * @throws PublicKeyFileException
275
	 * @throws VerificationFailedException
276
	 */
277 1
	public function createResponseByData(array $data, SignatureDataFormatter $responseSignatureDataFormatter): Response
278
	{
279 1
		$response = new Response(
280 1
			ResponseCode::get(ResponseCode::S200_OK),
281 1
			$data
282
		);
283
284 1
		$this->logRequest(HttpMethod::get(HttpMethod::GET), 'payment/response', [], [], $response);
285
286 1
		return new Response(
287 1
			$response->getResponseCode(),
288 1
			$this->decodeData($data, $responseSignatureDataFormatter),
289 1
			$response->getHeaders()
290
		);
291
	}
292
293
	/**
294
	 * @param mixed[] $data
295
	 * @param SignatureDataFormatter $signatureDataFormatter
296
	 * @return mixed[]
297
	 *
298
	 * @throws PrivateKeyFileException
299
	 * @throws SigningFailedException
300
	 */
301 15
	private function prepareData(array $data, SignatureDataFormatter $signatureDataFormatter): array
302
	{
303 15
		$data['dttm'] = (new DateTimeImmutable())->format('YmdHis');
304 15
		$data['signature'] = $this->cryptoService->signData($data, $signatureDataFormatter);
305
306 15
		return $data;
307
	}
308
309
	/**
310
	 * @param mixed[] $responseData
311
	 * @param SignatureDataFormatter $signatureDataFormatter
312
	 * @return mixed[]
313
	 *
314
	 * @throws InvalidSignatureException
315
	 * @throws PublicKeyFileException
316
	 * @throws VerificationFailedException
317
	 */
318 8
	private function decodeData(array $responseData, SignatureDataFormatter $signatureDataFormatter): array
319
	{
320 8
		if (!array_key_exists('signature', $responseData)) {
321 1
			throw new InvalidSignatureException($responseData);
322
		}
323
324 7
		$signature = $responseData['signature'];
325 7
		unset($responseData['signature']);
326
327 7
		if (!$this->cryptoService->verifyData($responseData, $signature, $signatureDataFormatter)) {
328 1
			throw new InvalidSignatureException($responseData);
329
		}
330
331 6
		return $responseData;
332
	}
333
334
	/**
335
	 * @param \SlevomatCsobGateway\Api\HttpMethod $method
336
	 * @param string $url
337
	 * @param mixed[] $queries
338
	 * @param mixed[]|null $requestData
339
	 * @param \SlevomatCsobGateway\Api\Response $response
340
	 */
341 16
	private function logRequest(HttpMethod $method, string $url, array $queries, array $requestData = null, Response $response)
342
	{
343 16
		if ($this->logger === null) {
344 11
			return;
345
		}
346
347 5
		$responseData = $response->getData();
348
349 5
		unset($requestData['signature']);
350 5
		unset($queries['signature']);
351 5
		unset($responseData['signature']);
352
353 5
		if (isset($responseData['extensions'])) {
354
			foreach ($responseData['extensions'] as $key => $extensionData) {
355
				unset($responseData['extensions'][$key]['signature']);
356
			}
357
		}
358
		$context = [
359
			'request' => [
360 5
				'method' => $method->getValue(),
361 5
				'queries' => $queries,
362 5
				'data' => $requestData,
363
			],
364
			'response' => [
365 5
				'code' => $response->getResponseCode()->getValue(),
366 5
				'data' => $responseData,
367 5
				'headers' => $response->getHeaders(),
368
			],
369
		];
370
371 5
		$this->logger->info($url, $context);
372 5
	}
373
374
}
375