This repository has been archived on 2018-10-11. You can view files and clone it, but cannot push or open issues or pull requests.
sleepy/Sleepy/Core/Input.php
2014-05-14 10:32:31 -04:00

416 lines
9.0 KiB
PHP
Executable File

<?php
/**
* Sleepy - a REST framework
*
*
* A PHP Rest Framework valuing convention over configuration,
* but aiming to be as flexible as possible
*
* @author Timothy J. Warren
*/
namespace Sleepy\Core;
/**
* Class for accessing request data
*
* @method array server()
* @method array env()
* @method array get()
* @method array post()
* @method array put()
* @method array delete()
* @method array cookie()
*/
class Input {
// --------------------------------------------------------------------------
// ! Stuff PHP Forgot
// --------------------------------------------------------------------------
/**
* The HTTP verb for the current request
*
* @var string
*/
protected $verb = 'get';
/**
* Class member for put data
*
* @var array
*/
protected $put = [];
/**
* Class member for delete data
*
* @var array
*/
protected $delete = [];
/**
* Class member for options data
*
* @var array
*/
protected $options = [];
// --------------------------------------------------------------------------
// ! Working around PHP for nicer usability
// --------------------------------------------------------------------------
/**
* An array mapping the function to the appropriate superglobal
*
* @var array
*/
protected $var_map = [];
/**
* The request's HTTP headers from the $_SERVER superglobal
*
* @var array
*/
protected $request_headers = [];
/**
* The request headers parsed into a more useful array
*
* @var array
*/
protected $parsed_headers = [];
// --------------------------------------------------------------------------
// ! Methods
// --------------------------------------------------------------------------
/**
* Instantiates the class
*
* @param array $config
*/
public function __construct($config = [])
{
// Type of HTTP request
$this->verb = \strtolower($_SERVER['REQUEST_METHOD']);
// Parse put and delete requests into input variables
// @codeCoverageIgnoreStart
if (isset($this->{$this->verb}))
{
$raw = \file_get_contents('php://input');
\parse_str($raw, $this->{$this->verb});
}
// Set mapping for superglobals, since
// variable variables don't seem to work
// with superglobals :/
$this->var_map = [
'get' => $_GET,
'post' => $_POST,
'server' => $_SERVER,
'env' => $_ENV,
'cookie' => $_COOKIE
];
// @codeCoverageIgnoreEnd
// Parse request headers from $_SERVER
foreach($_SERVER as $key => $val)
{
if (strpos($key, 'HTTP_') === 0)
{
$new_key = \strtolower(\strtr($key, [
'HTTP_' => '',
'_' => '-'
]));
$this->request_headers[$new_key] = $val;
}
}
}
/**
* Wrapper method for input arrays
*
* Actually works as get,post,put,delete,cookie,server,and env functions
* all wrapped up in one - because boilerplate is bad!
*
* @param string $name - name of input array
* @param array $args - function arguments
* @return mixed
* @throws \DomainException
*/
public function __call($name, $args=[])
{
// Predefine arguments for input array getters
if ( ! isset($args[0])) $args[0] = NULL;
if ( ! isset($args[1])) $args[1] = FILTER_SANITIZE_STRING;
$index = $args[0];
$filter = $args[1];
if (isset($this->var_map[$name]))
{
// Get a superglobal ($_VAR) value
return $this->get_superglobal_var($name, $index, $filter);
}
else if(isset($this->$name)) // @codeCoverageIgnoreStart
{
// Get a input variable not in a superglobal (eg. PUT/DELETE)
return $this->get_request_var($name, $index, $filter);
}
// @codeCoverageIgnoreEnd
// What kind of request are you trying to make?!
throw new \DomainException('Invalid input array.');
}
/**
* Return header(s) sent from the request
*
* @param string $index
* @return mixed
*/
public function header($index = NULL)
{
if ($index !== NULL)
{
$index = (\strtolower(\str_replace([' ', '_'], '-', $index)));
if (isset($this->request_headers[$index]))
{
return $this->request_headers[$index];
}
return NULL;
}
return $this->request_headers;
}
/**
* Return parsed header(s) sent from the request
*
* @param string $index
* @return mixed
*/
public function header_array($index = NULL)
{
if (empty($this->parsed_headers))
{
$this->parsed_headers = $this->parse_headers();
}
if ($index !== NULL)
{
$index = (str_replace([' ', '-'], '_', $index));
if (isset($this->parsed_headers[$index]))
{
return $this->parsed_headers[$index];
}
return NULL;
}
return $this->parsed_headers;
}
/**
* Convert headers to a parsed array of values
*
* @return array
*/
protected function parse_headers()
{
foreach($this->request_headers as $header => $value)
{
$has_semi = strpos($value, ';') !== FALSE;
$has_comma = strpos($value, ',') !== FALSE;
$has_eq = strpos($value, '=') !== FALSE;
// Parse the user agent separately
if ($header === 'user-agent')
{
$this->parsed_headers[$header] = $this->parse_user_agent($value);
continue;
}
// Parse accept-type headers separately as well
else if (strpos($header, 'accept') === 0)
{
$this->parsed_headers[$header] = $this->parse_accept_header($value);
}
// If the header has a comma, and not a semicolon, split on the comma
else if ( ! $has_semi && $has_comma)
{
$this->parsed_headers[$header] = explode(",", $value);
continue;
}
// Parse cookies and other headers with values like query strings
else if ($has_eq && ! $has_semi)
{
parse_str($value, $this->parsed_headers[$header]);
continue;
}
// Anything else, just leave it as a string
else
{
$this->parsed_headers[$header] = $value;
continue;
}
}
return $this->parsed_headers;
}
/**
* Parse a browser useragent into a set of useful key-value pairs
*
* @param string $ua_string
* @return array
*/
protected function parse_user_agent($ua_string)
{
$user_agent = [];
$slash_matches = [];
$slash_pattern = "`[A-Z]+/[0-9]+((\.[0-9]+)+)?`i";
$paren_matches = [];
$paren_pattern = "`\(.*?\)`i";
// Get all the foo/12.3 paterns from the user agent string
preg_match_all($slash_pattern, $ua_string, $slash_matches);
foreach($slash_matches[0] as $arr)
{
list($key, $version) = explode("/", $arr);
$user_agent['versions'][$key] = $version;
}
// Get all the info from parenthasized items
preg_match_all($paren_pattern, $ua_string, $paren_matches);
foreach($paren_matches[0] as $arr)
{
$arr = str_replace(['(',')'], '', $arr);
if (strpos($arr, ';') !== FALSE)
{
$user_agent['os'] = explode('; ', $arr);
}
else
{
$user_agent['misc'] = $arr;
}
}
return $user_agent;
}
/**
* Parse an accept-type header into an ordered list
* of values
*
* @param string $value
* @return array
*/
protected function parse_accept_header($value)
{
$q_types = [];
// A fake value so I can shift it off to have a 1-indexed array
$high_types = [];
$output = [];
$count = 1;
// Split into segments of different values
$groups = explode(',', $value);
foreach($groups as $group)
{
$group = \trim($group);
$pair = explode(';q=', $group);
if (count($pair) === 2)
{
list($val, $q) = $pair;
$q_types[$q] = $val;
}
else
{
$high_types[$count] = current($pair);
$count++;
}
}
// Add an additional fake value so we can
// have a 1-indexed array
$high_types[$count] = 'foo';
$high_types = array_reverse($high_types);
unset($high_types[0]);
$output = $q_types;
// Merge the arrays manually to maintain
// keys, and thus ordering
foreach($high_types as $k => $v)
{
$output[$k] = $v;
}
krsort($output, SORT_NUMERIC);
return $output;
}
/**
* Get input var(s) from non-defined superglobal
*
* @codeCoverageIgnore
* @param string $type - input array
* @param string $index - variable in the input array
* @param int $filter - PHP filter_var flag
* @return mixed
*/
protected function get_request_var($type, $index=NULL, $filter=FILTER_SANITIZE_STRING)
{
// If index is null, return the whole array
if ($index === NULL)
{
return ($filter !== NULL) ? \filter_var_array($this->$type, $filter) : $this->$type;
}
// Prevent errors for non-existant variables
if ( ! isset($this->$type[$index]))
{
return NULL;
}
return ($filter !== NULL) ? \filter_var($this->$type[$index], $filter) : $this->$type[$index];
}
/**
* Get index from superglobal
*
* @param string $type - superglobal
* @param string $index - variable in the superglobal
* @param int $filter - PHP filter_var flag
* @return mixed
*/
protected function get_superglobal_var($type, $index=NULL, $filter=FILTER_SANITIZE_STRING)
{
$var =& $this->var_map[$type];
// Return the whole array if the index is null
if ($index === NULL)
{
return ($filter !== NULL) ? \filter_var_array($var, $filter) : $var;
}
// Prevent errors for non-existant variables
if ( ! isset($var[$index]))
{
return NULL;
}
return ($filter !== NULL) ? \filter_var($var[$index], $filter) : $var[$index];
}
}
// End of core/Input.php