1: 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: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341:
<?php
namespace Antavo\RestClient;
/**
* A rather straightforward and lightweight, yet flexible helper class to
* perform operations on a RESTful API with cURL.
*
* @method mixed get(string $url, mixed $data = NULL, array $curl_options = array())
* Performs a GET request to the API.
* @method mixed post(string $url, mixed $data = NULL, array $curl_options = array())
* Performs a POST request to the API.
* @method mixed put(string $url, mixed $data = NULL, array $curl_options = array())
* Performs a PUT request to the API.
* @method mixed delete(string $url, mixed $data = NULL, array $curl_options = array())
* Performs a DELETE request to the API.
*/
class RestClient {
/**
* @var string Base URL to prepend when a request is made with relative
* path given.
*/
protected $base_url;
/**
* @var array
*/
protected $curl_options = array();
/**
* @var mixed
*/
protected $last_result;
/**
* @var string
*/
protected $last_result_headers;
/**
* @var array
*/
protected $last_result_info;
/**
* @var \Antavo\RestClient\Exceptions\Exception Stores the last processing
* error.
*/
protected $last_error;
/**
* Catches all invocations to inaccessible methods and forwards them to
* <tt>call()</tt>, with the original method name as request method.
*
* @param string $name Name of the invoked method.
* @param array $arguments Arguments.
* @return mixed
* @internal
*/
public function __call($name, $arguments) {
array_unshift($arguments, $name);
return call_user_func_array(array($this, 'call'), $arguments);
}
/**
* Performs an API request.
*
* @param string $method HTTP method to use.
* @param string $url URL to send request to. It is prefixed with
* the <tt>base_url</tt> unless it starts with a URL scheme.
* @param array|string $data Data to send with request. It is sent as a
* request entity where allowed, or appended as a query string otherwise.
* @param array $curl_options Additional cURL options for current request
* only.
* @return mixed
* @throws \Antavo\RestClient\Exceptions\Exception If an error occurred
* during processing response.
*/
public function call($method, $url, $data = NULL, array $curl_options = array()) {
// Converting HTTP method to uppercase.
$method = strtoupper($method);
// Converting data to application/x-www-form-urlencoded.
if (is_array($data)) {
$data = http_build_query($data);
}
// Extending URL.
if (!preg_match('#https{0,1}://#', $url)) {
$url = $this->base_url . '/' . ltrim($url, '/');
}
// Appending data as query string for requests that does not allow
// sending an entity.
if (strlen($data) && 'POST' != $method && 'PUT' != $method) {
$url .= (strpos($url, '?') === false ? '?' : '&') . $data;
}
// Initiating cURL resource.
$ch = curl_init();
// Setting default cURL options.
if (count($this->curl_options)) {
curl_setopt_array($ch, $this->curl_options);
}
// Appending cURL options given for this request only.
if (count($curl_options)) {
curl_setopt_array($ch, $curl_options);
}
// Setting up cURL for a POST request.
if ('POST' == $method || 'PUT' == $method) {
if (strlen($data)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($ch, CURLOPT_POST, true);
}
// These cURL options are not overrideable.
curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'captureHeader'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'TLSv1');
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
$this->setError();
$this->last_result_headers = NULL;
$this->last_result = curl_exec($ch);
$this->last_result_info = curl_getinfo($ch);
// Releasing resource.
curl_close($ch);
// Checking HTTP status code.
if (200 != $this->last_result_info['http_code']) {
$this->setError(new Exceptions\StatusCodeException(
preg_match(
'/^HTTP\/1.[01] \d+ (.+)/i',
$this->last_result_headers,
$matches
)
? $matches[1]
: 'No headers',
$this->last_result_info['http_code']
));
}
// Processing result.
$this->last_result = $this->processResult(
$this->last_result,
strtok($this->last_result_info['content_type'], ';')
);
if ($error = $this->getLastError()) {
throw $error;
}
return $this->last_result;
}
/**
* Callback method for cURL to capture header data.
*
* @param resource $resource
* @param string $string
* @return int Returns the number of bytes written.
*/
protected function captureHeader($resource, $string) {
$this->last_result_headers .= $string;
return strlen($string);
}
/**
* Returns base URL prepended to relative URL's for each request.
*
* @return string
*/
public function getBaseUrl() {
return $this->base_url;
}
/**
* Returns the last error occurred during processing the response.
*
* @return \Antavo\RestClient\Exceptions\Exception Returns <tt>NULL</tt>
* if there was no error.
*/
public function getLastError() {
return $this->last_error;
}
/**
* Returns body of last response, parsed according to
* <tt>Content-Type</tt> header.
*
* @return mixed
*/
public function getLastResult() {
return $this->last_result;
}
/**
* Returns headers of last response.
*
* @return string
*/
public function getLastResultHeaders() {
return $this->last_result_headers;
}
/**
* Returns info on last request.
*
* @return array For details see PHP <tt>curl_getinfo()</tt> manual.
* @link http://php.net/curl_getinfo PHP: curl_getinfo - Manual
*/
public function getLastResultInfo() {
return $this->last_result_info;
}
/**
* Processes result according to its content type.
*
* @param string $result Response body.
* @param string $content_type Content MIME-type without parameters (cut
* off at first semicolon).
* @return mixed
*/
protected function processResult($result, $content_type) {
switch ($content_type) {
case 'application/json':
return $this->processJsonResult($result);
case 'application/xml':
case 'text/xml':
return $this->processXmlResult($result);
default:
return $result;
}
}
/**
* Processes and returns JSON result.
*
* On JSON parse error it sets an {@see \Antavo\RestParserException} with
* error code & message then returns the original result string.
*
* @param string $result
* @return mixed
*/
protected function processJsonResult($result) {
$parsed_result = json_decode($result);
if (($error = json_last_error()) === JSON_ERROR_NONE) {
return $parsed_result;
}
$this->setError(new Exceptions\ParserException(
json_last_error_msg(),
$error
));
return $result;
}
/**
* Processes XML result.
*
* @param string $result
* @return mixed Returns a {@see \SimpleXMLElement} object on successful
* operation, or the original result string otherwise.
* @throws \LogicException If PHP SimpleXML extension is not loaded.
* @static
*/
protected function processXmlResult($result) {
// Checking for PHP SimpleXML extension.
if (!extension_loaded('SimpleXML')) {
throw new \LogicException(sprintf(
'%s needs the PHP SimpleXML extension in order to work',
get_called_class()
));
}
if (($parsed_result = simplexml_load_string($result)) !== false) {
return $parsed_result;
}
return $result;
}
/**
* Appends cURL option to defaults.
*
* @param string $option Option name. See cURL options for option names.
* @param mixed $value Option value. Providing a <tt>NULL</tt> removes an
* already set option.
* @return void
* @link http://php.net/curl_setopt List of cURL options.
*/
public function setCurlOption($option, $value = NULL) {
if (isset($value)) {
$this->curl_options[$option] = $value;
} else {
unset($this->curl_options[$option]);
}
}
/**
* Appends multiple cURL options to defaults.
*
* @param array $options cURL options as key-value pairs. For keys and
* their values see cURL options. Providing a <tt>NULL</tt> value removes
* an already set option.
* @return void
* @link http://php.net/curl_setopt List of cURL options.
*/
public function setCurlOptions(array $options) {
foreach ($options as $option => $value) {
$this->setCurlOption($option, $value);
}
}
/**
* Sets base URL which is to prepend to relative URL's for each request.
*
* @param string $base_url
*/
public function setBaseUrl($base_url) {
$this->base_url = $base_url;
}
/**
* Used to set an error without exiting the normal flow of an API call.
*
* Calling without argument or with a <tt>NULL</tt> value resets the stored
* last error.
*
* @param \Antavo\RestClient\Exceptions\Exception $exception
* @return void
* @see getLastError()
*/
public function setError(Exceptions\Exception $exception = NULL) {
$this->last_error = $exception;
}
}