Completed
Pull Request — master (#10)
by Jan
02:43
created

ApiClient   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 303
Duplicated Lines 18.15 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 6
Bugs 1 Features 0
Metric Value
wmc 26
c 6
b 1
f 0
lcom 1
cbo 14
dl 55
loc 303
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A get() 19 19 1
A post() 18 18 1
A put() 18 18 1
D request() 0 81 17
A createResponseByData() 0 13 1
A prepareData() 0 7 1
A decodeData() 0 15 3

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