1 <?php
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
26
27 namespace MyAllocator\phpsdk\src\Util;
28 use MyAllocator\phpsdk\src\MaBaseClass;
29 use MyAllocator\phpsdk\src\Util\Common;
30 use MyAllocator\phpsdk\src\Exception\ApiException;
31 use MyAllocator\phpsdk\src\Exception\ApiAuthenticationException;
32 use MyAllocator\phpsdk\src\Exception\ApiConnectionException;
33 use MyAllocator\phpsdk\src\Exception\InvalidRequestException;
34
35 36 37 38
39 class Requestor extends MaBaseClass
40 {
41 42 43
44 private $apiBase = 'api.myallocator.com';
45
46 47 48
49 public $version = '201408';
50
51 52 53
54 public $state = array(
55 'method' => null,
56 'request' => array(),
57 'response' => array()
58 );
59
60 61 62 63 64
65 public function __construct($cfg = null)
66 {
67 parent::__construct(array(
68 'cfg' => $cfg
69 ));
70 $this->debug_echo("\n\nConfiguration:\n");
71 $this->debug_print_r($this->config);
72 }
73
74 75 76 77 78 79 80 81 82 83 84
85 public function request($method, $url, $params = null)
86 {
87 if (!$params) {
88 $params = array();
89 } else {
90 $params = self::encodeObjects($params);
91 }
92
93
94 if (in_array('request', $this->config['dataResponse'])) {
95 $this->state['request']['body'] = $params;
96 }
97
98 99 100 101
102 $this->debug_echo("\nRequest (".$this->config['dataFormat']."):\n");
103 switch ($this->config['dataFormat']) {
104 case 'array':
105
106 $this->state['method'] = $params['_method'];
107
108 $this->debug_print_r($params);
109 try {
110 $params = json_encode($params);
111 } catch (Exception $e) {
112 $msg = 'JSON Encode Error - Invalid parameters: '.serialize($params);
113 throw new ApiException($msg, $this->state);
114 }
115 $this->debug_echo("\nRequest (json):\n");
116
117 case 'json':
118 $this->debug_echo($params);
119
120 $absUrl = $this->apiUrl($url, 'json');
121
122 $params = array('json' => $params);
123
124 $this->curlRequest(
125 $method,
126 $absUrl,
127 $params
128 );
129
130 $this->interpretResponseJSON();
131 break;
132 case 'xml':
133 $this->debug_echo($params);
134
135 $absUrl = $this->apiUrl($url, 'xml');
136 $this->debug_echo("\n\nURL:\n");
137 $this->debug_print_r($absUrl);
138
139 $params = 'xmlRequestString='.urlencode($params);
140
141 $this->curlRequest(
142 $method,
143 $absUrl,
144 $params
145 );
146
147 $this->interpretResponseXML();
148 break;
149 default:
150 $msg = 'Invalid data format: '.$this->config['dataFormat'];
151 throw new ApiException($msg, $this->state);
152 }
153
154 return $this->formatResponse();
155 }
156
157 158 159 160 161 162 163 164 165 166 167
168 private function curlRequest($method, $absUrl, $params)
169 {
170 $result = array();
171 $opts = array();
172 $curl = curl_init();
173
174 if ($method == 'post') {
175 $opts[CURLOPT_POST] = 1;
176 $opts[CURLOPT_POSTFIELDS] = $params;
177 } else {
178 throw new ApiException('Unsupported method '.$method, $this->state);
179 }
180
181 $absUrl = self::utf8($absUrl);
182 $opts[CURLOPT_URL] = $absUrl;
183 $opts[CURLOPT_RETURNTRANSFER] = true;
184 $opts[CURLOPT_CONNECTTIMEOUT] = 20;
185 $opts[CURLOPT_TIMEOUT] = 60;
186 $opts[CURLOPT_HEADER] = true;
187 $opts[CURLOPT_USERAGENT] = 'PHP SDK/1.0';
188
189
190 curl_setopt_array($curl, $opts);
191
192
193 if (in_array('timeRequest', $this->config['dataResponse'])) {
194 $this->state['request']['time'] = new \DateTime();
195 }
196
197
198 $curl_result = curl_exec($curl);
199
200
201 if (in_array('timeResponse', $this->config['dataResponse'])) {
202 $this->state['response']['time'] = new \DateTime();
203 }
204
205
206 if ($curl_result === false) {
207 $errno = curl_errno($curl);
208 $message = curl_error($curl);
209 curl_close($curl);
210 $this->handleCurlError($errno, $message);
211 }
212
213
214 $this->state['response']['code'] = curl_getinfo($curl, CURLINFO_HTTP_CODE);
215 $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
216 $this->state['response']['headers'] = substr($curl_result, 0, $header_size);
217 $this->state['response']['body_raw'] = substr($curl_result, $header_size);
218 curl_close($curl);
219
220 return $this->state;
221 }
222
223 224 225 226 227 228 229
230 private function interpretResponseJSON()
231 {
232 $this->debug_echo("\n\nResponse (json):\n");
233 $this->debug_print_r($this->state['response']['body_raw']);
234
235
236 if ($this->config['dataFormat'] == 'array') {
237 try {
238 $this->state['response']['body'] = json_decode(
239 $this->state['response']['body_raw'],
240 true
241 );
242 } catch (Exception $e) {
243 $msg = 'Invalid response body from API: '.$this->state['response']['body_raw']
244 . '(HTTP response code was '.$this->state['response']['code'].')';
245 throw new ApiException($msg, $this->state);
246 }
247 $this->debug_echo("\n\nResponse (array):\n");
248 $this->debug_print_r($this->state['response']['body']);
249 } else {
250 $this->state['response']['body'] = $this->state['response']['body_raw'];
251 }
252
253 if ($this->state['response']['code'] < 200 ||
254 $this->state['response']['code'] >= 300
255 ) {
256 $this->handleHttpError();
257 }
258
259 return $this->state;
260 }
261
262 263 264 265 266
267 private function interpretResponseXML()
268 {
269 $this->debug_echo("\n\nResponse (xml):\n".$this->state['response']['body_raw']);
270
271 $this->state['response']['body'] = $this->state['response']['body_raw'];
272
273 if ($this->state['response']['code'] < 200 ||
274 $this->state['response']['code'] >= 300
275 ) {
276 $this->handleHttpError();
277 }
278
279 return $this->state;
280 }
281
282 283 284 285 286 287 288 289
290 private function handleCurlError($errno, $message)
291 {
292 $apiBase = $this->apiBase;
293 switch ($errno) {
294 case CURLE_COULDNT_CONNECT:
295 case CURLE_COULDNT_RESOLVE_HOST:
296 case CURLE_OPERATION_TIMEOUTED:
297 $msg = 'Could not connect to MyAllocator (' . $apiBase . '). '
298 . 'Please check your internet connection and try again.';
299 break;
300 default:
301 $msg = 'Unexpected error communicating with MyAllocator. '
302 . 'If this problem persists, let us know at '
303 . 'support@myallocator.com.';
304 }
305
306 $msg .= "\n\n(Network error [errno $errno]: $message)";
307 throw new ApiConnectionException($msg, $this->state);
308 }
309
310 311 312 313 314 315 316 317 318 319 320
321 private function handleHttpError()
322 {
323 $msg = 'HTTP API Error (HTTP response code was '.$this->state['response']['code'].')';
324 switch ($this->state['response']['code']) {
325 case 404:
326 throw new InvalidRequestException($msg, $this->state);
327 case 401:
328 throw new ApiAuthenticationException($msg, $this->state);
329 default:
330 throw new ApiException($msg, $this->state);
331 }
332 }
333
334 335 336 337 338 339 340 341
342 private function apiUrl($url = '', $format = 'json')
343 {
344 if (!$url) {
345 return false;
346 }
347
348 $absUrl = sprintf('https://%s/pms/v%d/%s/%s',
349 $this->apiBase,
350 $this->version,
351 $format,
352 $url);
353
354 return (string) $absUrl;
355 }
356
357 358 359 360 361 362 363
364 public static function utf8($value)
365 {
366 if (is_string($value) &&
367 mb_detect_encoding($value, 'UTF-8', TRUE) != 'UTF-8'
368 ) {
369 return utf8_encode($value);
370 } else {
371 return $value;
372 }
373 }
374
375 376 377 378 379 380 381 382
383 public static function encode($arr, $prefix=null)
384 {
385 if (!is_array($arr)) {
386 return $arr;
387 }
388
389 $r = array();
390 foreach ($arr as $k => $v) {
391 if (is_null($v)) {
392 continue;
393 }
394
395 if ($prefix && $k && !is_int($k)) {
396 $k = $prefix . '[' . $k . ']';
397 } else if ($prefix) {
398 $k = $prefix . '[]';
399 }
400
401 if (is_array($v)) {
402 $r[] = self::encode($v, $k, true);
403 } else {
404 $r[] = urlencode($k) . '=' . urlencode($v);
405 }
406 }
407
408 return implode('&', $r);
409 }
410
411 412 413 414 415 416 417
418 private static function encodeObjects($d)
419 {
420 if ($d instanceof Api) {
421 return self::utf8(Common::getClassName(get_class($d)));
422 } else if ($d === true) {
423 return 'true';
424 } else if ($d === false) {
425 return 'false';
426 } else if (is_array($d)) {
427 $res = array();
428 foreach ($d as $k => $v) {
429 $res[$k] = self::encodeObjects($v);
430 }
431 return $res;
432 } else {
433 return self::utf8($d);
434 }
435 }
436
437 438 439 440 441
442 private function formatResponse()
443 {
444 $return = array();
445
446
447 if (isset($this->state['method'])) {
448 $return['method'] = $this->state['method'];
449 }
450
451
452 if (!empty($this->state['request'])) {
453 $return['request'] = $this->state['request'];
454 }
455
456
457 if (!empty($this->state['response'])) {
458 $return['response'] = $this->state['response'];
459 }
460
461 return $return;
462 }
463 }
464