Skip to content

Instantly share code, notes, and snippets.

@Quacky2200
Last active July 24, 2020 17:39
Show Gist options
  • Save Quacky2200/74d280bf828fcd68f326a4b5aab603c3 to your computer and use it in GitHub Desktop.
Save Quacky2200/74d280bf828fcd68f326a4b5aab603c3 to your computer and use it in GitHub Desktop.
Generic request class, mostly for API requests
<?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