Client::request()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 13
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 20
rs 9.8333
1
<?php
2
3
namespace alekciy\ofd\providers\taxcom;
4
5
use alekciy\ofd\Json;
6
use alekciy\ofd\providers\taxcom\Request\Login;
7
use alekciy\ofd\Request;
8
use GuzzleHttp\Client as httpClient;
9
use GuzzleHttp\Exception\GuzzleException;
10
use Psr\Http\Message\ResponseInterface;
11
12
class Client
13
{
14
	/** @var httpClient HTTP клиент для низкоуровневой работы с API */
15
	protected $httpClient = null;
16
17
	/** @var Credentials Реквизиты доступа */
18
	protected $credentials = null;
19
20
	/** @var string Номер договора */
21
	protected $agreementNumber = '';
22
23
	/** @var string Сессионный токен */
24
	protected $sessionToken = '';
25
26
	/** @var string Версия клиента */
27
	private $version = '0.1.0';
28
29
	/** @var int Номер запроса */
30
	static private $requestNumber = 0;
31
32
	/** @var string Директория куда будет сохраняться тело ответа */
33
	static private $bodyStorageDir = '';
34
35
	/**
36
	 * @param Credentials $credentials Реквизиты доступа.
37
	 * @param string $agreementNumber  Номер договора.
38
	 * @param string $sessionToken     Сессионный токен доступа.
39
	 * @param array $clientConfig      Дополнительную настройки для Guzzle клиента.
40
	 */
41
	public function __construct(Credentials $credentials, string $agreementNumber = '', string $sessionToken = '', array $clientConfig = [])
42
	{
43
		$this->credentials = $credentials;
44
		$this->agreementNumber = $agreementNumber;
45
		$this->sessionToken = $sessionToken;
46
47
		$httpClientConfig = [
48
			'base_uri' => 'https://' . $credentials->domain,
49
			'defaults' => [
50
				'headers' => [
51
					'Content-Type' => 'application/json',
52
					'Accept' => 'application/json',
53
				],
54
			],
55
		];
56
		$this->httpClient = new httpClient(array_merge($clientConfig, $httpClientConfig));
57
	}
58
59
	/**
60
	 * Задает директорию куда будут сохраняться тела всех ответов. Метод полезен при создании фикстур для тестов.
61
	 *
62
	 * @param string $dirPath
63
	 * @return void
64
	 * @throws Exception
65
	 */
66
	static public function setBodyStorageDir(string $dirPath)
67
	{
68
		$absDirPath = realpath($dirPath);
69
		if ($absDirPath === false
70
			|| !is_writable($absDirPath)
71
		) {
72
			throw new Exception("Директория «{$absDirPath}» недоступна для записи");
73
		}
74
		self::$bodyStorageDir = $absDirPath;
75
	}
76
77
	/**
78
	 * Залогиниться получив сессионный токен.
79
	 *
80
	 * @return void
81
	 * @throws Exception
82
	 * @throws GuzzleException
83
	 */
84
	public function login()
85
	{
86
		$login = new Login([
87
			'login' => $this->credentials->login,
88
			'password' => $this->credentials->password,
89
		]);
90
		if (!empty($this->agreementNumber)) {
91
			$login->agreementNumber = $this->agreementNumber;
92
		}
93
		$response = $this->sendRequest($login);
94
		$body = Json::decode($response->getBody()->getContents(), true);
95
		if ($response->getStatusCode() != 200) {
96
			$code = (integer) $body['apiErrorCode'];
97
			throw new Exception(
98
				Exception::$codeList[$code] ?? 'Неизвестная ошибка',
99
				$code ?? 0
100
			);
101
		}
102
		if (empty($body['sessionToken'])) {
103
			throw new Exception('Не удалось получить сессионный токен');
104
		}
105
		$this->sessionToken = $body['sessionToken'];
106
	}
107
108
	/**
109
	 * Отправить запрос.
110
	 *
111
	 * @param Request $endpoint
112
	 * @return ResponseInterface
113
	 * @throws GuzzleException
114
	 * @throws Exception
115
	 */
116
	protected function sendRequest(Request $endpoint): ResponseInterface
117
	{
118
		$response =  $this->httpClient->request(
119
			$endpoint->method,
120
			$endpoint->getPath(),
121
			[
122
				'debug' => $endpoint->debug,
123
				'exceptions' => false,
124
				'headers' => [
125
					'Session-Token' => $this->sessionToken,
126
					'Integrator-ID' => $this->credentials->integratorId,
127
					'Accept'        => 'application/json',
128
					'User-Agent'    => 'PHP-OFD-SDK/' . $this->version,
129
				],
130
				'query' => $endpoint->getQuery(),
131
				'json' => $endpoint->getBody(),
132
			]
133
		);
134
		++self::$requestNumber;
135
		if (!empty(self::$bodyStorageDir)) {
136
			$fileName = sprintf('%05d_%s', self::$requestNumber, str_replace('/', '_', $endpoint->getPath()) . '.json');
137
			$body = (string) $response->getBody()->getContents();
138
			$response->getBody()->rewind();
139
			file_put_contents(self::$bodyStorageDir . '/' . $fileName, $body . PHP_EOL);
140
		}
141
142
		return $response;
143
	}
144
145
	/**
146
	 * Шлет запрос и обрабатывает результаты.
147
	 *
148
	 * @param Request $endpoint
149
	 * @return array
150
	 * @throws GuzzleException
151
	 * @throws Exception
152
	 * @see https://lk-ofd.taxcom.ru/ApiHelp/index.html?3___.htm Обработка ошибок
153
	 */
154
	public function request($endpoint): array
155
	{
156
		$response = $this->sendRequest($endpoint);
157
		$body = Json::decode($response->getBody()->getContents(), true);
158
		$errorCode = $body['apiErrorCode'] ?? 0;
159
		// Истек срок действия маркера доступа
160
		if ($errorCode == 2109) {
161
			// Обновляем токен
162
			$this->login();
163
			$response = $this->sendRequest($endpoint);
164
			$body = Json::decode($response->getBody()->getContents(), true);
165
			$errorCode = $body['apiErrorCode'] ?? 0;
166
		}
167
		if (!empty($errorCode)) {
168
			throw new Exception(
169
				get_class($endpoint) . ' ошибка: ' . ($body['details'] ?? $body['commonDescription']),
170
				$errorCode
171
			);
172
		}
173
		return $body;
174
	}
175
}
176