Last active
July 24, 2020 17:39
-
-
Save Quacky2200/74d280bf828fcd68f326a4b5aab603c3 to your computer and use it in GitHub Desktop.
Generic request class, mostly for API requests
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* REST test scripts | |
* @license MIT | |
*/ | |
class Request { | |
public $protocol; | |
public $host; | |
public $path; | |
public $proxyURL; | |
public $decoders; | |
public $userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"; | |
public $allowInsecure = false; | |
public $headers = array(); | |
// set to Basic for user/pass authorization | |
public $authorize = null; | |
public $defaultMime = 'application/json'; | |
/** | |
* Creates a Request object | |
*/ | |
public function __construct($protocol = null, $host = null, $path = null) { | |
$this->protocol = $protocol ?: 'https'; | |
$this->host = $host ?: 'localhost'; | |
$this->path = $path ?: ''; | |
$this->decoders = array( | |
"application/xml" => function($res) { | |
// Decode an XML response | |
return json_decode(json_encode((array)simplexml_load_string($res)), true); | |
}, | |
"application/json" => function($res) { | |
// Decode a JSON response | |
return json_decode($res, true); | |
}, | |
"application/vnd.php.serialized" => function($res) { | |
// Deserialize a PHP object | |
return unserialize($res); | |
}, | |
// "application/x-www-form-urlencoded" => function($res) { | |
// // Decode a url encoded string | |
// $result = $this->parseUrl(urldecode($res)); | |
// return $result; | |
// }, | |
); | |
} | |
/** | |
* Make a request to the API with the following information. | |
* | |
* You can use params to contain uri query information, but you cannot use | |
* both $uri and $params to create GET query parameters. It's probably best | |
* to stick to $params so that it will be easier to manage them. | |
* | |
* @param string $uri The endpoint | |
* @param array $params Extra data for the request (GET/POST data) | |
* @param string $method The request type (GET/POST/PUT/DELETE) | |
* @param string $format The response format (JSON/XML) | |
* @return Array | |
*/ | |
public function makeRequest($uri = "/", $params = null, $method = "GET", $mime = null) { | |
// Build URL | |
$uri = $this->protocol . "://" . $this->host . $this->path . $uri; | |
$this->headers += array( | |
"Accept" => $mime, | |
"User-Agent" => $this->userAgent | |
); | |
if ($this->authorize) { | |
$this->headers['Authorization'] = $this->authorize; | |
} | |
$params = $params ?: array(); | |
if ($params !== null && !is_array($params)) { | |
throw new \Exception( | |
'Invalid type ' . gettype($mime) . | |
', expected array for request parameters' | |
); | |
} | |
$mime = $mime ?: $this->defaultMime; | |
if ($mime !== null && !is_string($mime)) { | |
throw new \Exception( | |
'Invalid type ' . gettype($mime) . | |
', expected string for mime' | |
); | |
} | |
if (function_exists('curl_init')) { | |
$opts = array( | |
CURLOPT_CUSTOMREQUEST => $method, | |
CURLOPT_HEADER => false, | |
CURLOPT_HTTPHEADER => $this->combineHeaders($this->headers, 'array'), | |
CURLOPT_FAILONERROR => true, | |
CURLOPT_FOLLOWLOCATION => true, | |
CURLOPT_RETURNTRANSFER => true, | |
CURLOPT_PROXY => $this->proxyURL, | |
CURLOPT_TIMEOUT => 10, | |
CURLOPT_CONNECTTIMEOUT => 5, | |
); | |
if ($this->allowInsecure) { | |
$opts += array( | |
CURLOPT_SSL_VERIFYPEER => false, | |
CURLOPT_SSL_VERIFYHOST => false, | |
); | |
} | |
// Only send content in PUT and POST requests | |
if (in_array(strtoupper($method), array("PUT", "POST"))){ | |
$opts[CURLOPT_POSTFIELDS] = http_build_query($params, '', '&'); | |
} else { | |
$uri .= "?" . http_build_query($params); | |
} | |
// Start the connection | |
$ch = curl_init($uri); | |
// Make sure we can connect | |
if (($ch = curl_init($uri)) === false) { | |
throw new RuntimeException("Cannot connect to HOST: {$uri}"); | |
} | |
// Try to make the request and get data out | |
if (curl_setopt_array($ch, $opts) === false || ($data = curl_exec($ch)) === false) { | |
$err = curl_error($ch); | |
curl_close($ch); | |
throw new RuntimeException("{$err} \"{$uri}\""); | |
} | |
} elseif (ini_get('allow_url_fopen')) { | |
if (in_array(strtoupper($method), array("PUT", "POST"))) { | |
$this->headers["Content-Type"] = "application/x-www-form-urlencoded"; | |
$opts['http']['content'] = http_build_query($request, '', '&'); | |
} else if (strpos($uri, '?') === false) { | |
$uri .= "?" . http_build_query($params); | |
} | |
$opts = array( | |
'http' => array( | |
'header' => $this->combineHeaders($this->headers, 'string'), | |
'method' => $method, | |
'user_agent' => $this->userAgent, | |
'proxy' => $this->proxyURL, | |
'timeout' => 5, | |
), | |
); | |
if ($this->allowInsecure) { | |
$opts['ssl'] = array( | |
"verify_peer" => false, | |
"verify_peer_name" => false, | |
); | |
} | |
$context = stream_context_create($opts); | |
if (($data = @file_get_contents($uri, false, $context)) === false) { | |
$error = error_get_last()['message']; | |
preg_match("/(?:HTTP request failed! HTTP\/[0-9].[0-9] ([0-9a-zA-Z ]+))/", $error, $specific); | |
$error = (@$specific[1] ?: $error); | |
throw new RuntimeException("Cannot connect to HOST as $error: {$uri}"); | |
} | |
} else { | |
throw new RuntimeException('No means of communication with REST API, please enable CURL or HTTP Stream Wrappers'); | |
} | |
if (!$data) { | |
throw RuntimeException("The response was empty"); | |
} | |
$decoder = $this->getDecoder($mime); | |
if (!$decoder) { | |
throw new RuntimeException("No decoder is present for mime type {$mime}"); | |
} | |
$decoded = $decoder($data); | |
if (!$decoded) { | |
throw new RuntimeException("The response cannot be decoded into the mime type {$mime}. Response: {$data}"); | |
} | |
return $decoded; | |
} | |
/** | |
* Combines headers into a string | |
* @param array $headers Dictionary of header keys and values | |
* @param string $type Return type | |
* @return mixed String or Array containing combined KVPs | |
*/ | |
public function combineHeaders($headers, $type = 'string') { | |
foreach($headers as $key => &$value) { | |
if (is_string($key)) { | |
$value = "$key: $value"; | |
} | |
} | |
if ($type == 'string') { | |
// returns 'User-Agent: Chrome\nHost: localhost\n' etc... | |
return implode(array_values($headers), "\n"); | |
} else if ($type == 'array') { | |
// returns array('User-Agent: Chrome', 'Host: localhost'), etc... | |
return array_values($headers); | |
} else { | |
throw new \Exception('Invalid combineHeaders type'); | |
} | |
} | |
/** | |
* Parse headers from a string into an array. | |
* Not yet implemented | |
* @return [type] [description] | |
*/ | |
public function parseHeaders() { | |
// TODO? | |
throw new \Exception('Not yet implemented'); | |
} | |
/** | |
* Adds a KVP to the headers for future requests. | |
* @param string $key Header key | |
* @param string $value Header value | |
*/ | |
public function addHeader($key, $value) { | |
$this->headers[$key] = $value; | |
return $this; | |
} | |
/** | |
* Remove a KVP from the headers for future requests. | |
* @param string $key Header key | |
* @param string $value Header value | |
*/ | |
public function removeHeader($key) { | |
if (isset($this->headers[$key])) { | |
unset($this->headers[$key]); | |
} | |
return $this; | |
} | |
/** | |
* This removes any authorization header currently being used. | |
* @return $this | |
*/ | |
public function removeAuthorization() { | |
$this->authorize = null; | |
return $this; | |
} | |
/** | |
* Sets basic authorization for username and password | |
* @param string $username User's Username | |
* @param string $password User's password | |
* @return $this | |
*/ | |
public function setBasicAuthorization($username, $password) { | |
$this->authorize = "Basic " . base64_encode("{$username}:{$password}"); | |
return $this; | |
} | |
/** | |
* Set authorization header with authorization string (e.g. token) | |
* @param string $auth authorization string | |
* @return $this | |
*/ | |
public function setOtherAuthorization($auth) { | |
$this->authorize = $auth; | |
return $this; | |
} | |
/** | |
* Sets the default mime type. | |
* @param string $mime Default mime | |
* @return $this | |
*/ | |
public function setDefaultMime($mime) { | |
if (is_string($mime)) { | |
$this->defaultMime = $mime; | |
} | |
return $this; | |
} | |
/** | |
* Adds a decoding mechanism to the supported list of decoders | |
* @param string $mime The mime type in the format of <MIME_type/MIME_subtype> | |
* @param closure $decodeLamda The decoding mechanism to support the mime type | |
* @return $this | |
*/ | |
public function addDecoder($mime, $decodeLamda){ | |
$this->decoders[$mime] = $decodeLamda; | |
return $this; | |
} | |
/** | |
* Gets a decoding lamda mechanism for a known mime type | |
* @param string $mime The mime type in the format of <MIME_type/MIME_subtype> | |
* @return closure The decoding mechanism | |
*/ | |
public function getDecoder($mime) { | |
if (array_key_exists($mime, $this->decoders)){ | |
return $this->decoders[$mime]; | |
} else { | |
return null; | |
} | |
} | |
/** | |
* Returns all known mime types supported by the REST API | |
* @return array list of <MIME_type/MIME_subtype> | |
*/ | |
public function getMimeTypes(){ | |
return array_keys($this->decoders); | |
} | |
// @see https://gist.github.com/rubo77/6821632 to skip max_input_vars error | |
/** | |
* @param $string | |
* @return array|bool | |
*/ | |
function parseUrl($string) { | |
if($string==='') { | |
return false; | |
} | |
$result = array(); | |
// Find the pairs "name=value" | |
$pairs = explode('&', $string); | |
foreach ($pairs as $pair) { | |
$dynamicKey = (false !== strpos($pair, '[]=')) || (false !== strpos($pair, '%5B%5D=')); | |
// use the original parse_str() on each element | |
parse_str($pair, $params); | |
$k = key($params); | |
if (!isset($result[$k])) { | |
$result += $params; | |
} else { | |
$result[$k] = $this->arrayMergeRecursiveDistinct($result[$k], $params[$k], $dynamicKey); | |
} | |
} | |
return $result; | |
} | |
/** | |
* @param array $array1 | |
* @param array $array2 | |
* @param $dynamicKey | |
* @return array | |
*/ | |
function arrayMergeRecursiveDistinct(array &$array1, array &$array2, $dynamicKey) { | |
$merged = $array1; | |
foreach ($array2 as $key => &$value) { | |
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) { | |
$merged[$key] = $this->arrayMergeRecursiveDistinct($merged[$key], $value, $dynamicKey); | |
} else { | |
if ($dynamicKey) { | |
if (!isset( $merged[$key])) { | |
$merged[$key] = $value; | |
} else { | |
if (is_array($merged[$key])) { | |
$merged[$key] = array_merge_recursive($merged[$key], $value); | |
} else { | |
$merged[] = $value; | |
} | |
} | |
} else { | |
$merged[$key] = $value; | |
} | |
} | |
} | |
return $merged; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment