Completed
Pull Request — master (#10)
by Jan
08:05 queued 05:12
created

ApiClient::get()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 16

Duplication

Lines 19
Ratio 100 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 19
loc 19
rs 9.4285
cc 1
eloc 16
nc 1
nop 6
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
						/** @var ResponseExtensionHandler $handler */
206
						$handler = $extensions[$name];
207
						unset($extensionData['extension']);
208
						$decodedExtensions[$name] = $handler->createResponse($this->decodeData($extensionData, $handler->getSignatureDataFormatter()));
209
					}
210
				}
211
			}
212
			$responseData = $this->decodeData($response->getData(), $responseSignatureDataFormatter);
213
			unset($responseData['extensions']);
214
215
			return new Response(
216
				$response->getResponseCode(),
217
				$responseData,
218
				$response->getHeaders(),
219
				$decodedExtensions
220
			);
221
222
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S303_SEE_OTHER)) {
223
			return new Response(
224
				$response->getResponseCode(),
225
				null,
226
				$response->getHeaders()
227
			);
228
229
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S400_BAD_REQUEST)) {
230
			throw new BadRequestException($response);
231
232
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S403_FORBIDDEN)) {
233
			throw new ForbiddenException($response);
234
235
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S404_NOT_FOUND)) {
236
			throw new NotFoundException($response);
237
238
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S405_METHOD_NOT_ALLOWED)) {
239
			throw new MethodNotAllowedException($response);
240
241
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S429_TOO_MANY_REQUESTS)) {
242
			throw new TooManyRequestsException($response);
243
244
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S503_SERVICE_UNAVAILABLE)) {
245
			throw new ServiceUnavailableException($response);
246
		}
247
248
		throw new InternalErrorException($response);
249
	}
250
251
	/**
252
	 * @param mixed[] $data
253
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
254
	 * @return Response
255
	 *
256
	 * @throws InvalidSignatureException
257
	 * @throws PrivateKeyFileException
258
	 * @throws SigningFailedException
259
	 * @throws PublicKeyFileException
260
	 * @throws VerificationFailedException
261
	 */
262
	public function createResponseByData(array $data, SignatureDataFormatter $responseSignatureDataFormatter): Response
263
	{
264
		$response = new Response(
265
			new ResponseCode(ResponseCode::S200_OK),
266
			$data
267
		);
268
269
		return new Response(
270
			$response->getResponseCode(),
271
			$this->decodeData($data, $responseSignatureDataFormatter),
272
			$response->getHeaders()
273
		);
274
	}
275
276
	/**
277
	 * @param mixed[] $data
278
	 * @param SignatureDataFormatter $signatureDataFormatter
279
	 * @return mixed[]
280
	 *
281
	 * @throws PrivateKeyFileException
282
	 * @throws SigningFailedException
283
	 */
284
	private function prepareData(array $data, SignatureDataFormatter $signatureDataFormatter): array
285
	{
286
		$data['dttm'] = (new DateTimeImmutable())->format('YmdHis');
287
		$data['signature'] = $this->cryptoService->signData($data, $signatureDataFormatter);
288
289
		return $data;
290
	}
291
292
	/**
293
	 * @param array $responseData
294
	 * @param SignatureDataFormatter $signatureDataFormatter
295
	 * @return mixed[]
296
	 *
297
	 * @throws InvalidSignatureException
298
	 * @throws PublicKeyFileException
299
	 * @throws VerificationFailedException
300
	 */
301
	private function decodeData(array $responseData, SignatureDataFormatter $signatureDataFormatter): array
302
	{
303
		if (!array_key_exists('signature', $responseData)) {
304
			throw new InvalidSignatureException($responseData);
305
		}
306
307
		$signature = $responseData['signature'];
308
		unset($responseData['signature']);
309
310
		if (!$this->cryptoService->verifyData($responseData, $signature, $signatureDataFormatter)) {
311
			throw new InvalidSignatureException($responseData);
312
		}
313
314
		return $responseData;
315
	}
316
317
}
318