1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @author Dmitry Gladyshev <[email protected]> |
4
|
|
|
* @date 16/08/2016 18:06 |
5
|
|
|
*/ |
6
|
|
|
|
7
|
|
|
namespace Yandex\Direct\Transport\Json; |
8
|
|
|
|
9
|
|
|
use GuzzleHttp\Client; |
10
|
|
|
use GuzzleHttp\ClientInterface; |
11
|
|
|
use GuzzleHttp\Exception\RequestException; |
12
|
|
|
use GuzzleHttp\HandlerStack; |
13
|
|
|
use GuzzleHttp\MessageFormatter; |
14
|
|
|
use GuzzleHttp\Middleware; |
15
|
|
|
use LSS\Array2XML; |
16
|
|
|
use Psr\Log\LoggerAwareInterface; |
17
|
|
|
use Psr\Log\LoggerInterface; |
18
|
|
|
use Psr\Log\NullLogger; |
19
|
|
|
use Yandex\Direct\ConfigurableInterface; |
20
|
|
|
use Yandex\Direct\ConfigurableTrait; |
21
|
|
|
use Yandex\Direct\Exception\InvalidArgumentException; |
22
|
|
|
use Yandex\Direct\Exception\RuntimeException; |
23
|
|
|
use Yandex\Direct\Exception\TransportRequestException; |
24
|
|
|
use Yandex\Direct\Transport\RequestInterface; |
25
|
|
|
use Yandex\Direct\Transport\Response; |
26
|
|
|
use Yandex\Direct\Transport\TransportInterface; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Class JsonTransport |
30
|
|
|
* @package Yandex\Direct\Transport |
31
|
|
|
*/ |
32
|
|
|
class Transport implements TransportInterface, LoggerAwareInterface, ConfigurableInterface |
33
|
|
|
{ |
34
|
|
|
use ConfigurableTrait; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @var string |
38
|
|
|
*/ |
39
|
|
|
private $baseUrl = 'https://api.direct.yandex.com'; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @var string |
43
|
|
|
*/ |
44
|
|
|
private $reportsXmlSchema = 'https://api.direct.yandex.com/v5/reports.xsd'; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @var bool |
48
|
|
|
*/ |
49
|
|
|
private $enableReportValidation = false; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @var ClientInterface |
53
|
|
|
*/ |
54
|
|
|
private $httpClient; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Custom Service urls |
58
|
|
|
* @var array |
59
|
|
|
*/ |
60
|
|
|
private $serviceUrls = [ |
61
|
|
|
'Reports' => '/v5/reports' |
62
|
|
|
]; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @var array |
66
|
|
|
*/ |
67
|
|
|
private $headers = [ |
68
|
|
|
'Content-Type' => 'application/json; charset=utf-8' |
69
|
|
|
]; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* @var LoggerInterface |
73
|
|
|
*/ |
74
|
|
|
private $logger; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @var MessageFormatter |
78
|
|
|
*/ |
79
|
|
|
private $logMessageFormatter; |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* JsonTransport constructor. |
83
|
|
|
* |
84
|
|
|
* @param array $options |
85
|
|
|
*/ |
86
|
38 |
|
public function __construct(array $options = []) |
87
|
|
|
{ |
88
|
38 |
|
$this->setOptions($options); |
89
|
38 |
|
} |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* @inheritdoc |
93
|
|
|
*/ |
94
|
2 |
|
public function getServiceUrl($serviceName) |
95
|
|
|
{ |
96
|
2 |
|
if (isset($this->serviceUrls[$serviceName])) { |
97
|
|
|
// If service url is absolute |
98
|
1 |
|
if (preg_match('#http[s]*://#u', $this->serviceUrls[$serviceName])) { |
99
|
1 |
|
return $this->serviceUrls[$serviceName]; |
100
|
|
|
} |
101
|
1 |
|
return $this->baseUrl . $this->serviceUrls[$serviceName]; |
102
|
|
|
} |
103
|
|
|
|
104
|
2 |
|
return $this->baseUrl . '/json/v5/' . strtolower($serviceName); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* @param array $headers |
109
|
|
|
*/ |
110
|
1 |
|
public function setHeaders(array $headers) |
111
|
|
|
{ |
112
|
1 |
|
$this->headers = array_merge($this->headers, $headers); |
113
|
1 |
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* @inheritdoc |
117
|
|
|
*/ |
118
|
1 |
|
public function setLogger(LoggerInterface $logger) |
119
|
|
|
{ |
120
|
1 |
|
$this->logger = $logger; |
121
|
1 |
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* @inheritdoc |
125
|
|
|
*/ |
126
|
1 |
|
public function request(RequestInterface $request) |
127
|
|
|
{ |
128
|
1 |
|
try { |
129
|
1 |
|
$client = $this->getHttpClient(); |
130
|
|
|
|
131
|
1 |
|
$httpResponse = $client->request('POST', $this->getServiceUrl($request->getService()), [ |
132
|
1 |
|
'headers' => $this->prepareHeaders($request), |
133
|
1 |
|
'body' => $this->prepareBody($request) |
134
|
1 |
|
]); |
135
|
|
|
|
136
|
1 |
|
$httpResponseHeaders = $httpResponse->getHeaders(); |
137
|
|
|
|
138
|
1 |
|
return new Response([ |
139
|
1 |
|
'service' => $request->getService(), |
140
|
1 |
|
'method' => $request->getMethod(), |
141
|
1 |
|
'headers' => $httpResponse->getHeaders(), |
142
|
1 |
|
'body' => $httpResponse->getBody()->__toString(), |
143
|
1 |
|
'code' => $httpResponse->getStatusCode(), |
144
|
1 |
|
'requestId' => isset($httpResponseHeaders['RequestId']) ? current($httpResponseHeaders['RequestId']) : null, |
145
|
1 |
|
'units' => isset($httpResponseHeaders['Units']) ? current($httpResponseHeaders['Units']) : null |
146
|
1 |
|
]); |
147
|
|
|
} catch (RequestException $e) { |
148
|
|
|
$this->getLogger()->error("Transport error: {$e->getMessage()} [CODE: {$e->getCode()}]"); |
149
|
|
|
throw new TransportRequestException( |
150
|
|
|
$e->getMessage(), |
151
|
|
|
$e->getCode(), |
152
|
|
|
$e->getRequest()->getHeaders(), |
153
|
|
|
$e->getRequest()->getBody()->__toString(), |
154
|
|
|
$e->hasResponse() ? $e->getResponse()->getHeaders() : [], |
155
|
|
|
$e->hasResponse() ? $e->getResponse()->getBody()->__toString() : '', |
156
|
|
|
$e->getPrevious() |
157
|
|
|
); |
158
|
|
|
} catch (\Exception $e) { |
159
|
|
|
$this->getLogger()->error("Runtime error: {$e->getMessage()} [CODE: {$e->getCode()}]"); |
160
|
|
|
throw new RuntimeException($e->getMessage(), $e->getCode(), $e->getPrevious()); |
161
|
|
|
} |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* @return ClientInterface |
166
|
|
|
*/ |
167
|
1 |
|
private function getHttpClient() |
168
|
|
|
{ |
169
|
1 |
|
if ($this->httpClient === null) { |
170
|
|
|
$this->httpClient = new Client([ |
171
|
|
|
'base_uri' => $this->baseUrl, |
172
|
|
|
'handler' => $this->getHttpHandlers() |
173
|
|
|
]); |
174
|
|
|
} |
175
|
1 |
|
return $this->httpClient; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* @return LoggerInterface |
180
|
|
|
*/ |
181
|
1 |
|
private function getLogger() |
182
|
|
|
{ |
183
|
|
|
// Use stub if logger is not initialized |
184
|
1 |
|
if ($this->logger === null) { |
185
|
|
|
$this->logger = new NullLogger; |
186
|
|
|
} |
187
|
1 |
|
return $this->logger; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* @return MessageFormatter |
192
|
|
|
*/ |
193
|
1 |
|
private function getMessageFormatter() |
194
|
|
|
{ |
195
|
1 |
|
if ($this->logMessageFormatter === null) { |
196
|
1 |
|
$this->logMessageFormatter = new MessageFormatter(MessageFormatter::DEBUG); |
197
|
1 |
|
} |
198
|
1 |
|
return $this->logMessageFormatter; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* @return HandlerStack |
203
|
|
|
*/ |
204
|
1 |
|
private function getHttpHandlers() |
205
|
|
|
{ |
206
|
1 |
|
$stack = HandlerStack::create(); |
207
|
1 |
|
$stack->push(Middleware::log( |
208
|
1 |
|
$this->getLogger(), |
209
|
1 |
|
$this->getMessageFormatter() |
210
|
1 |
|
)); |
211
|
1 |
|
return $stack; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* @param RequestInterface $request |
216
|
|
|
* @return array |
217
|
|
|
*/ |
218
|
1 |
|
private function prepareHeaders(RequestInterface $request) |
219
|
|
|
{ |
220
|
1 |
|
$headers = array_merge([ |
221
|
1 |
|
'Authorization' => 'Bearer ' . $request->getCredentials()->getToken(), |
222
|
1 |
|
'Client-Login' => $request->getCredentials()->getLogin(), |
223
|
1 |
|
], $this->headers, $request->getHeaders()); |
224
|
|
|
|
225
|
1 |
|
if ($request->getService() === self::SERVICE_AGENCY_CLIENTS) { |
226
|
|
|
unset($headers['Client-Login']); |
227
|
|
|
} |
228
|
|
|
|
229
|
1 |
|
return $headers; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* @param RequestInterface $request |
234
|
|
|
* @return string |
235
|
|
|
*/ |
236
|
1 |
|
private function prepareBody(RequestInterface $request) |
237
|
|
|
{ |
238
|
1 |
|
switch ($request->getService()) { |
239
|
1 |
|
case self::SERVICE_REPORTS: |
240
|
|
|
return $this->prepareXmlBody($request); |
241
|
1 |
|
default: |
242
|
1 |
|
return $this->prepareJsonBody($request); |
243
|
1 |
|
} |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* @param RequestInterface $request |
248
|
|
|
* @return string |
249
|
|
|
*/ |
250
|
1 |
|
private function prepareJsonBody(RequestInterface $request) |
251
|
|
|
{ |
252
|
1 |
|
return json_encode( |
253
|
1 |
|
array_merge(['method' => $request->getMethod()], $request->getParams()), |
254
|
1 |
|
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT |
255
|
1 |
|
); |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* @param RequestInterface $request |
260
|
|
|
* @return string |
261
|
|
|
* @throws InvalidArgumentException |
262
|
|
|
*/ |
263
|
|
|
private function prepareXmlBody(RequestInterface $request) |
264
|
|
|
{ |
265
|
|
|
$xml = Array2XML::createXML( |
266
|
|
|
'ReportDefinition', |
267
|
|
|
['@attributes' => ['xmlns' => 'http://api.direct.yandex.com/v5/reports']] + $request->getParams()['params'] |
268
|
|
|
); |
269
|
|
|
|
270
|
|
|
if ($this->enableReportValidation) { |
271
|
|
|
$this->validateReportXml($xml); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
// var_dump(trim($xml->saveXML())); die; |
|
|
|
|
275
|
|
|
|
276
|
|
|
return str_replace(PHP_EOL, '', $xml->saveXML()); |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* @param \DOMDocument $xml |
281
|
|
|
* @throws InvalidArgumentException |
282
|
|
|
*/ |
283
|
|
|
private function validateReportXml(\DOMDocument $xml) |
284
|
|
|
{ |
285
|
|
|
libxml_use_internal_errors(true); |
286
|
|
|
if (!$xml->schemaValidate($this->reportsXmlSchema)) { |
287
|
|
|
$error = libxml_get_last_error(); |
288
|
|
|
libxml_clear_errors(); |
289
|
|
|
throw new InvalidArgumentException($error->message, $error->code); |
290
|
|
|
} |
291
|
|
|
} |
292
|
|
|
} |
293
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.