Completed
Push — master ( fd2349...bbc868 )
by Milan
21s queued 11s
created

Ares::loadByIdentificationNumbers()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 37
rs 9.0168
c 0
b 0
f 0
cc 5
nc 8
nop 2
1
<?php declare(strict_types=1);
2
3
namespace h4kuna\Ares;
4
5
use h4kuna\Ares\Exceptions\ConnectionException;
6
use h4kuna\Ares\Exceptions\IdentificationNumberNotFoundException;
7
use GuzzleHttp;
8
9
class Ares
10
{
11
	public const RESULT_FAILED = 'failed';
12
	public const RESULT_SUCCESS = 'success';
13
	public const URL = 'https://wwwinfo.mfcr.cz/cgi-bin/ares/darv_bas.cgi';
14
	public const POST_URL = 'https://wwwinfo.mfcr.cz/cgi-bin/ares/xar.cgi';
15
	private const POST_IDENTIFICATION_NUMBERS_LIMIT = 100; // in one post request can be max 100 identification numbers
16
17
	/** @var IFactory */
18
	private $factory;
19
20
	/** @var DataProvider */
21
	private $dataProvider;
22
23
24
	public function __construct(IFactory $factory = null)
25
	{
26
		if ($factory === null) {
27
			$factory = new Factory();
28
		}
29
		$this->factory = $factory;
30
	}
31
32
33
	/**
34
	 * @param array<string>|array<int> $identificationNumbers
35
	 * @param array<string, string|int|float> $options
36
	 * @return array{failed: array<Error>, success: array<Data>}
0 ignored issues
show
Documentation introduced by
The doc-type array{failed: could not be parsed: Unknown type name "array{failed:" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
37
	 */
38
	public function loadByIdentificationNumbers(array $identificationNumbers, array $options = []): array
39
	{
40
		$client = $this->factory->createGuzzleClient($options);
41
		$offset = 0;
42
		$output = [
43
			self::RESULT_FAILED => [],
44
			self::RESULT_SUCCESS => [],
45
		];
46
47
		$identificationNumbersCount = count($identificationNumbers);
48
		while (($identificationNumbersCount - $offset) > 0) {
49
			$identificationNumbersBatch = array_slice($identificationNumbers, $offset, self::POST_IDENTIFICATION_NUMBERS_LIMIT, true);
50
			$offset += self::POST_IDENTIFICATION_NUMBERS_LIMIT;
51
52
			$responseData = $this->sendDoseOfInsRequest($client, $identificationNumbersBatch);
53
54
			foreach ($responseData as $item) {
55
				$D = $item->children('D', true);
56
				$pid = (int) $D->PID->__toString();
57
58
				try {
59
					if ($D->E->asXML() !== false) {
60
						$DE = $D->E->children('D', true);
61
						throw new IdentificationNumberNotFoundException(trim($DE->ET->__toString()), $DE->EK->__toString());
62
					}
63
64
					$this->processXml($D->VBAS, $this->getDataProvider()->prepareData());
65
66
					$output[self::RESULT_SUCCESS][$pid] = $this->getData();
67
				} catch (IdentificationNumberNotFoundException $exception) {
68
					$output[self::RESULT_FAILED][$pid] = new Error((string) $identificationNumbers[$pid], $exception->getCode(), $exception->getMessage());
69
				}
70
			}
71
		}
72
73
		return $output;
74
	}
75
76
77
	/**
78
	 * Load fresh data.
79
	 * @param array<string, mixed> $options
80
	 * @throws IdentificationNumberNotFoundException
81
	 */
82
	public function loadData(string $in, array $options = []): Data
83
	{
84
		$this->loadXML($in, $options);
85
		return $this->getData();
86
	}
87
88
89
	/**
90
	 * Get temporary data.
91
	 */
92
	public function getData(): Data
93
	{
94
		return $this->getDataProvider()->getData();
95
	}
96
97
98
	/**
99
	 * Load XML and fill Data object
100
	 * @param array<string, mixed> $options
101
	 * @throws IdentificationNumberNotFoundException
102
	 */
103
	private function loadXML(string $in, array $options): void
104
	{
105
		$client = $this->factory->createGuzzleClient($options);
106
		try {
107
			$xmlSource = $client->request('GET', $this->createUrl($in))
108
				->getBody()
109
				->getContents();
110
		} catch (\Throwable $e) {
111
			throw new ConnectionException($e->getMessage(), $e->getCode(), $e);
112
		}
113
		$xml = @simplexml_load_string($xmlSource);
114
		if (!$xml) {
115
			throw new ConnectionException();
116
		}
117
118
		$ns = $xml->getDocNamespaces();
119
		$answer = $xml->children($ns['are'])->children($ns['D']);
120
		$this->parseErrorAnswer($xml, $in);
121
		$this->processXml($answer->VBAS, $this->getDataProvider()->prepareData());
122
	}
123
124
125
	protected function processXml(\SimpleXMLElement $xml, DataProvider $dataProvider): void
126
	{
127
		$dataProvider->setIN((string) $xml->ICO)
128
			->setTIN((string) $xml->DIC)
129
			->setCompany((string) $xml->OF)
130
			->setZip(self::exists($xml->AA, 'PSC'))
131
			->setStreet(self::exists($xml->AA, 'NU'))
132
			->setCity(self::exists($xml->AA, 'N'))
133
			->setHouseNumber(self::exists($xml->AA, 'CD'), self::exists($xml->AA, 'CO'), self::exists($xml->AA, 'CA'))
134
			->setCityPost(self::exists($xml->AA, 'NMC'))
135
			->setCityDistrict(self::exists($xml->AA, 'NCO'))
136
			->setIsPerson(self::exists($xml->PF, 'KPF'))
137
			->setCreated((string) $xml->DV)
138
			->setNace(self::existsArray($xml->Nace, 'NACE'));
139
140
		$dataProvider->setDissolved(isset($xml->DZ) ? (string) $xml->DZ : null);
141
142
		if (isset($xml->ROR)) {
143
			$dataProvider
144
				->setFileNumber((string) $xml->ROR->SZ->OV)
145
				->setCourt((string) $xml->ROR->SZ->SD->T);
146
		} else {
147
			$dataProvider
148
				->setFileNumber('')
149
				->setCourt('');
150
		}
151
	}
152
153
154
	private function createUrl(string $inn): string
155
	{
156
		$parameters = [
157
			'ico' => $inn,
158
			'aktivni' => 'false',
159
		];
160
		return self::URL . '?' . http_build_query($parameters);
161
	}
162
163
164
	private function getDataProvider(): DataProvider
165
	{
166
		if ($this->dataProvider === null) {
167
			$this->dataProvider = $this->factory->createDataProvider();
168
		}
169
		return $this->dataProvider;
170
	}
171
172
173
	private static function exists(\SimpleXMLElement $element, string $property): string
174
	{
175
		return isset($element->{$property}) ? ((string) $element->{$property}) : '';
176
	}
177
178
179
	private static function existsArray(\SimpleXMLElement $element, string $property): array
180
	{
181
		return isset($element->{$property}) ? ((array) $element->{$property}) : [];
182
	}
183
184
185
	private function parseErrorAnswer(\SimpleXMLElement $answer, string $in): void
186
	{
187
		$errorMessage = self::xmlValue($answer, '//D:ET[1]');
188
		$errorCode = self::xmlValue($answer, '//D:EK[1]');
189
		if ($errorMessage === null && $errorCode === null) {
190
			return;
191
		}
192
193
		// 61 - subject disappeared
194
		// 71 - not exists
195
		if (empty($errorMessage)) {
196
			throw new ConnectionException();
197
		}
198
		throw new IdentificationNumberNotFoundException(sprintf('IN "%s", Error: #%s, %s', $in, $errorCode, $errorMessage), $in);
199
	}
200
201
202
	private static function xmlValue(\SimpleXMLElement $xml, string $xpath): ?string
203
	{
204
		$result = $xml->xpath($xpath);
205
		if ($result === false || !isset($result[0])) {
206
			return null;
207
		}
208
		return trim((string) $result[0]);
209
	}
210
211
212
	/**
213
	 * @param array<string>|array<int> $identificationNumbersBatch
214
	 */
215
	public function sendDoseOfInsRequest(
216
		GuzzleHttp\Client $client,
217
		array $identificationNumbersBatch
218
	): \SimpleXMLElement
219
	{
220
		try {
221
			$body = $client->request('POST', self::POST_URL, [
222
				'headers' => [
223
					'Content-type' => 'application/xml',
224
				],
225
				'body' => $this->factory->createBodyFactory()->createBodyContent($identificationNumbersBatch),
226
			])->getBody()->getContents();
227
		} catch (\Throwable $e) {
228
			throw new ConnectionException($e->getMessage(), $e->getCode(), $e);
229
		}
230
231
		$simpleXml = @simplexml_load_string($body, "SimpleXMLElement", 0, 'SOAP-ENV', true);
232
		if ($simpleXml === false) {
233
			throw new ConnectionException();
234
		}
235
		$simpleXml->registerXPathNamespace('SOAP-ENV', 'http://schemas.xmlsoap.org/soap/envelope/');
236
237
		return $simpleXml->children('SOAP-ENV', true)
238
			->Body
239
			->children('are', true)
240
			->children('are', true);
241
	}
242
243
}
244