MyAllocator PMS PHP SDK
  • Namespace
  • Class
  • Tree

Namespaces

  • MyAllocator
    • phpsdk
      • src
        • Api
        • Exception
        • Object
        • Util
  • PHP

Classes

  • Common
  • Requestor
  1 <?php
  2 /**
  3  * Copyright (C) 2014 MyAllocator
  4  *
  5  * A copy of the LICENSE can be found in the LICENSE file within
  6  * the root directory of this library.  
  7  *
  8  * Permission is hereby granted, free of charge, to any person obtaining a
  9  * copy of this software and associated documentation files (the "Software"),
 10  * to deal in the Software without restriction, including without limitation
 11  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 12  * and/or sell copies of the Software, and to permit persons to whom the
 13  * Software is furnished to do so, subject to the following conditions:
 14  *
 15  * The above copyright notice and this permission notice shall be included
 16  * in all copies or substantial portions of the Software.
 17  *
 18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 19  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 23  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 24  * IN THE SOFTWARE.
 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  * The Requestor class is responsible for preparing and sending an API reqest,
 37  * as well as parsing and handling the response.
 38  */
 39 class Requestor extends MaBaseClass
 40 {
 41     /**
 42      * @var string The MyAllocator API base url.
 43      */
 44     private $apiBase = 'api.myallocator.com';
 45 
 46     /**
 47      * @var string The API version. 
 48      */
 49     public $version = '201408';
 50 
 51     /**
 52      * @var array Request and response state data.
 53      */
 54     public $state = array(
 55         'method' => null,
 56         'request' => array(), // time, body
 57         'response' => array() // time, code, headers, body, body_raw
 58     );
 59 
 60     /**
 61      * The constructor passes potential configuration parameters to MaBaseClass.
 62      *
 63      * @param array $cfg API configuration parameters.
 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      * Send an API request, interpret and return the response.
 76      *
 77      * @param string $method HTTP method.
 78      * @param string $url API endpoint.
 79      * @param array|null $params API parameters.
 80      *
 81      * @return mixed API response, code, headers (XML only).
 82      *
 83      * @throws MyAllocator\phpsdk\src\Exception\ApiException
 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         // Set request data if configured
 94         if (in_array('request', $this->config['dataResponse'])) {
 95             $this->state['request']['body'] = $params;
 96         }
 97 
 98         /*
 99          * Send request based on dataFormat. Format 'array'
100          * is json_encoded and sent as json.
101          */
102         $this->debug_echo("\nRequest (".$this->config['dataFormat']."):\n");
103         switch ($this->config['dataFormat']) {
104             case 'array':
105                 // Set data method
106                 $this->state['method'] = $params['_method'];
107                 // Encode parameters
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                 // Intentionally dropping into json case
117             case 'json':
118                 $this->debug_echo($params); 
119                 // Generate absolute url
120                 $absUrl = $this->apiUrl($url, 'json');
121                 // Format params for curl request POSTFIELDS
122                 $params = array('json' => $params);
123                 // Send request
124                 $this->curlRequest(
125                     $method,
126                     $absUrl,
127                     $params
128                 );
129                 // Process response
130                 $this->interpretResponseJSON();
131                 break;
132             case 'xml':
133                 $this->debug_echo($params); 
134                 // Generate absolute url
135                 $absUrl = $this->apiUrl($url, 'xml');
136                 $this->debug_echo("\n\nURL:\n");
137                 $this->debug_print_r($absUrl);
138                 // Format params for curl request POSTFIELDS
139                 $params = 'xmlRequestString='.urlencode($params);
140                 // Send request
141                 $this->curlRequest(
142                     $method,
143                     $absUrl,
144                     $params
145                 );
146                 // Process response
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      * Send a JSON or XML CURL request.
159      *
160      * @param string $method HTTP method.
161      * @param string $absUrl The absolute endpoint URL.
162      * @param array|null $params API parameters.
163      *
164      * @return mixed API response, code, headers.
165      *
166      * @throws MyAllocator\phpsdk\src\Exception\ApiException
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         // Apply options
190         curl_setopt_array($curl, $opts);
191 
192         // Set request time if configured
193         if (in_array('timeRequest', $this->config['dataResponse'])) {
194             $this->state['request']['time'] = new \DateTime();
195         }
196 
197         // Sent request
198         $curl_result = curl_exec($curl);
199 
200         // Set response time if configured
201         if (in_array('timeResponse', $this->config['dataResponse'])) {
202             $this->state['response']['time'] = new \DateTime();
203         }
204 
205         // Error handling
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         // Parse code, headers, body from response
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      * Process a JSON CURL response.
225      *
226      * @return mixed API response. The format depends on dataFormat.
227      *
228      * @throws MyAllocator\phpsdk\src\Exception\ApiException
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         // Convert response from json to array format if required
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      * Process a XML CURL response.
264      *
265      * @return mixed API response. The format depends on dataFormat.
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      * Handle a CURL error resulting from a request.
284      *
285      * @param integer $errno The CURL error code.
286      * @param string $message The CURL error nessage.
287      *
288      * @throws MyAllocator\phpsdk\src\Exception\ApiConnectionException
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      * Handle a HTTP error resulting from a request.
312      *
313      * @param mixed $body The HTTP response body.
314      * @param integer $code The HTTP error code.
315      * @param string $resp The JSON encoded response body.
316      *
317      * @throws MyAllocator\phpsdk\src\Exception\InvalidRequestException
318      * @throws MyAllocator\phpsdk\src\Exception\APIAuthenticationException
319      * @throws MyAllocator\phpsdk\src\Exception\ApiException
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      * Get the absolute URL for an API request.
336      *
337      * @param string $url The API method endpoint.
338      * @param string $format The request data format.
339      *
340      * @return string The absolute API URL.
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      * Convert a string to UTF-8 encoding.
359      *
360      * @param string $value The string to encode.
361      *
362      * @return string The UTF-8 encoded string.
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      * URL encode URL array parameters.
377      *
378      * @param array $arr The array to encode.
379      * @param string $prefix A key prefix.
380      *
381      * @return string The URL encoded string.
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      * Encode an object.
413      *
414      * @param mixed $d The object to encode.
415      *
416      * @return mixed The encoded object.
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      * Format a response from available state data.
439      *
440      * @return array Formatted response.
441      */
442     private function formatResponse()
443     {
444         $return = array();
445 
446         // Method
447         if (isset($this->state['method'])) {
448             $return['method'] = $this->state['method'];
449         }
450 
451         // Request
452         if (!empty($this->state['request'])) {
453             $return['request'] = $this->state['request'];
454         }
455 
456         // Response
457         if (!empty($this->state['response'])) {
458             $return['response'] = $this->state['response'];
459         }
460 
461         return $return;
462     }
463 }
464 
MyAllocator PMS PHP SDK API documentation generated by ApiGen