Initial Commit
This commit is contained in:
commit
4280489558
2
.gitignore
vendored
Executable file
2
.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
vendor/*
|
||||
coverage/*
|
391
Sleepy/Core/Input.php
Executable file
391
Sleepy/Core/Input.php
Executable file
@ -0,0 +1,391 @@
|
||||
<?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
|
||||
* @package Sleepy/core
|
||||
*/
|
||||
|
||||
namespace Sleepy\Core;
|
||||
|
||||
/**
|
||||
* Class for accessing request data
|
||||
*/
|
||||
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 = [];
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// ! 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
|
||||
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
|
||||
];
|
||||
|
||||
// Parse request headers from $_SERVER
|
||||
foreach($_SERVER as $key => $val)
|
||||
{
|
||||
if (strpos($key, 'HTTP_') === 0)
|
||||
{
|
||||
$new_key = strtolower(str_replace('HTTP_', '', $key));
|
||||
$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))
|
||||
{
|
||||
// Get a input variable not in a superglobal (eg. PUT/DELETE)
|
||||
return $this->get_request_var($name, $index, $filter);
|
||||
}
|
||||
|
||||
// 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 = (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;
|
||||
}
|
||||
// For headers with commas and semicolons, break first on commas,
|
||||
// then on semicolons
|
||||
else if ($has_semi && $has_comma)
|
||||
{
|
||||
$values = explode(",", $value);
|
||||
foreach($values as &$v)
|
||||
{
|
||||
if (strpos($v, ";") !== FALSE)
|
||||
{
|
||||
$v = explode(";", $v);
|
||||
}
|
||||
}
|
||||
|
||||
$this->parsed_headers[$header] = $values;
|
||||
}
|
||||
// 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)
|
||||
{
|
||||
$output = [];
|
||||
$index = 0;
|
||||
|
||||
// Split into segments of different values
|
||||
$groups = explode(',', $value);
|
||||
|
||||
foreach($groups as $group)
|
||||
{
|
||||
$pair = explode(';q=', $group);
|
||||
|
||||
if (count($pair) === 2)
|
||||
{
|
||||
list($val, $q) = $pair;
|
||||
$output[$q] = $val;
|
||||
}
|
||||
else
|
||||
{
|
||||
$index++;
|
||||
$output[$index] = current($pair);
|
||||
}
|
||||
}
|
||||
|
||||
ksort($output, SORT_NATURAL);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get input var(s) from non-defined superglobal
|
||||
*
|
||||
* @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
|
181
Sleepy/Core/Output.php
Executable file
181
Sleepy/Core/Output.php
Executable file
@ -0,0 +1,181 @@
|
||||
<?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
|
||||
* @package Sleepy/core
|
||||
*/
|
||||
|
||||
namespace Sleepy\core;
|
||||
|
||||
/**
|
||||
* Default output class
|
||||
*/
|
||||
class Output {
|
||||
|
||||
/**
|
||||
* The data to serialize and output
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* A list of HTTP headers to send with a response
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $headers = [];
|
||||
|
||||
/**
|
||||
* The serialization object for the current data type
|
||||
*
|
||||
* @var Sleepy\core\aType
|
||||
*/
|
||||
protected $type_wrapper;
|
||||
|
||||
/**
|
||||
* The input object
|
||||
*
|
||||
* @var Sleepy\core\Input
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// ! Methods
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Create the output object
|
||||
*
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(Input $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the data to the client
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
// Output the headers
|
||||
$this->_output_headers();
|
||||
|
||||
// Echo the response
|
||||
echo $this->type_wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header to be output
|
||||
*
|
||||
* @param mixed $header
|
||||
* @return void
|
||||
*/
|
||||
public function set_header($header)
|
||||
{
|
||||
if (is_array($header))
|
||||
{
|
||||
array_merge($this->headers, $header);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->headers[] = $header;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data to be output to the endpoint
|
||||
*
|
||||
* @param string $type - The datatype to send
|
||||
* @param mixed $data
|
||||
* @return void
|
||||
*/
|
||||
public function set_data($type = 'json', $data)
|
||||
{
|
||||
// Get the appropriate output format for the client
|
||||
// And set the data
|
||||
$this->get_accepted_type($type, $data);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// ! Private helper methods
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get the type more accepted for output
|
||||
*
|
||||
* @param mixed $types
|
||||
* @param mixed $data
|
||||
* @return void
|
||||
*/
|
||||
protected function get_accepted_type($types, $data)
|
||||
{
|
||||
$types = (array) $types;
|
||||
$types = array_map('strtoupper', $types);
|
||||
|
||||
$headers = $this->input->header_array();
|
||||
$accept = array_flip($headers['accept']);
|
||||
|
||||
$type_map = [];
|
||||
$accepted = [];
|
||||
$classes = [];
|
||||
|
||||
foreach($types as $t)
|
||||
{
|
||||
$type_class = "Sleepy\\Type\\{$t}";
|
||||
$classes[$type_class] = new $type_class($data);
|
||||
$mime = $classes[$type_class]->get_mime();
|
||||
|
||||
$type_map[$mime] = $type_class;
|
||||
}
|
||||
|
||||
foreach($accept as $type => $q)
|
||||
{
|
||||
if (array_key_exists($type, $type_map))
|
||||
{
|
||||
$accepted[$q] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
krsort($accepted);
|
||||
|
||||
$class = $type_map[current($accepted)];
|
||||
$this->type_wrapper = $classes[$class];
|
||||
|
||||
// Make sure to set the content-type header
|
||||
if (empty($this->headers))
|
||||
{
|
||||
$mime = $this->type_wrapper->get_mime();
|
||||
$this->set_header("Content-type: {$mime}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the applicable response headers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _output_headers()
|
||||
{
|
||||
foreach($this->headers as $name => $val)
|
||||
{
|
||||
if (is_numeric($name))
|
||||
{
|
||||
$output_header = $val;
|
||||
}
|
||||
else
|
||||
{
|
||||
$output_header = implode(": ", [$name, $val]);
|
||||
}
|
||||
|
||||
@header($output_header);
|
||||
}
|
||||
}
|
||||
}
|
||||
// End of core/Output.php
|
26
Sleepy/Core/Router.php
Executable file
26
Sleepy/Core/Router.php
Executable file
@ -0,0 +1,26 @@
|
||||
<?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
|
||||
* @package Sleepy/core
|
||||
*/
|
||||
|
||||
namespace Sleepy\Core;
|
||||
|
||||
class Router {
|
||||
|
||||
protected $class;
|
||||
protected $method;
|
||||
|
||||
public function __construct($config = [])
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// End of core/Router.php
|
78
Sleepy/Core/aType.php
Executable file
78
Sleepy/Core/aType.php
Executable file
@ -0,0 +1,78 @@
|
||||
<?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
|
||||
* @package Sleepy/core
|
||||
*/
|
||||
|
||||
namespace Sleepy\Core;
|
||||
|
||||
use Sleepy\Exception\NotImplementedException;
|
||||
|
||||
/**
|
||||
* Abstract class with helpful functionality implementing type functionality
|
||||
*/
|
||||
abstract class aType implements iType {
|
||||
|
||||
/**
|
||||
* The data in the current type wrapper
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* The mime type to output for the current type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $mime;
|
||||
|
||||
/**
|
||||
* Create the type object with the specified data
|
||||
*
|
||||
* @param mixed $data
|
||||
* @throws NotImplementedException
|
||||
*/
|
||||
public function __construct($data = NULL)
|
||||
{
|
||||
|
||||
if (empty($this->mime))
|
||||
{
|
||||
throw new NotImplementedException("Output types must have a mime type defined.");
|
||||
}
|
||||
|
||||
if ( ! is_null($data))
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mime type needed
|
||||
* for the current type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_mime()
|
||||
{
|
||||
return $this->mime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the data as a string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->serialize($this->data);
|
||||
}
|
||||
|
||||
}
|
||||
// End of core/aType.php
|
37
Sleepy/Core/iType.php
Executable file
37
Sleepy/Core/iType.php
Executable file
@ -0,0 +1,37 @@
|
||||
<?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
|
||||
* @package Sleepy/core
|
||||
*/
|
||||
|
||||
namespace Sleepy\core;
|
||||
|
||||
/**
|
||||
* Interface for output formats
|
||||
*/
|
||||
interface iType {
|
||||
|
||||
/**
|
||||
* Convert the data to the output format
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return string
|
||||
*/
|
||||
public function serialize($data);
|
||||
|
||||
/**
|
||||
* Convert the format to php data
|
||||
*
|
||||
* @param string $data_string
|
||||
* @return mixed
|
||||
*/
|
||||
public function unserialize($data_string);
|
||||
|
||||
}
|
||||
// End of core/iType.php
|
33
Sleepy/Exception/NotImplementedException.php
Executable file
33
Sleepy/Exception/NotImplementedException.php
Executable file
@ -0,0 +1,33 @@
|
||||
<?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
|
||||
* @package Sleepy/exceptions
|
||||
*/
|
||||
|
||||
namespace Sleepy\Exception;
|
||||
|
||||
/**
|
||||
* Exception for enforcing properties that need to be defined,
|
||||
* or covering methods that are not yet implemented.
|
||||
*/
|
||||
class NotImplementedException extends \LogicException {
|
||||
|
||||
/**
|
||||
* Have a default message for the exception
|
||||
*
|
||||
* @param string $message - The exception message
|
||||
*/
|
||||
public function __construct($message = "Hmm...you stumbled onto something that is not implemented.")
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// End of exceptions/NotImplementedException.php
|
80
Sleepy/Type/HTML.php
Normal file
80
Sleepy/Type/HTML.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?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
|
||||
* @package Sleepy/types
|
||||
*/
|
||||
|
||||
namespace Sleepy\Type;
|
||||
|
||||
use Sleepy\Core\aType;
|
||||
|
||||
/**
|
||||
* Class for HTML output
|
||||
*/
|
||||
class HTML extends aType {
|
||||
|
||||
/**
|
||||
* The mime type for output
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $mime = 'text/html';
|
||||
|
||||
/**
|
||||
* Convert the data into the output format
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return string
|
||||
*/
|
||||
public function serialize($data = NULL)
|
||||
{
|
||||
if ( ! is_null($data))
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
else
|
||||
{
|
||||
$data = $this->data;
|
||||
}
|
||||
|
||||
if (is_string($data)) return $data;
|
||||
|
||||
// Lets use JSON as an output format if the value isn't scalar
|
||||
return '<pre>' . json_encode($data, JSON_PRETTY_PRINT) . '</pre>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the encoded data to a native format
|
||||
*
|
||||
* @param string $data_string
|
||||
* @return object
|
||||
*/
|
||||
public function unserialize($data_string)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// ! Helper function
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Function to simplify type instantiation
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return JSON
|
||||
*/
|
||||
function HTML($data = NULL)
|
||||
{
|
||||
return new JSON($data);
|
||||
}
|
||||
|
||||
// End of types/JSON.php
|
77
Sleepy/Type/JSON.php
Executable file
77
Sleepy/Type/JSON.php
Executable file
@ -0,0 +1,77 @@
|
||||
<?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
|
||||
* @package Sleepy/types
|
||||
*/
|
||||
|
||||
namespace Sleepy\Type;
|
||||
|
||||
use Sleepy\Core\aType;
|
||||
|
||||
/**
|
||||
* Class for JSON output
|
||||
*/
|
||||
class JSON extends aType {
|
||||
|
||||
/**
|
||||
* The mime type for output
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $mime = 'application/json';
|
||||
|
||||
/**
|
||||
* Convert the data into the output format
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return string
|
||||
*/
|
||||
public function serialize($data = NULL)
|
||||
{
|
||||
if ( ! is_null($data))
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
else
|
||||
{
|
||||
$data = $this->data;
|
||||
}
|
||||
|
||||
return json_encode($data, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the encoded data to a native format
|
||||
*
|
||||
* @param string $data_string
|
||||
* @return object
|
||||
*/
|
||||
public function unserialize($data_string)
|
||||
{
|
||||
return json_decode($data_string);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// ! Helper function
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Function to simplify type instantiation
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return JSON
|
||||
*/
|
||||
function JSON($data = NULL)
|
||||
{
|
||||
return new JSON($data);
|
||||
}
|
||||
|
||||
// End of types/JSON.php
|
64
Sleepy/Type/YAML.php
Executable file
64
Sleepy/Type/YAML.php
Executable file
@ -0,0 +1,64 @@
|
||||
<?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
|
||||
* @package Sleepy/types
|
||||
*/
|
||||
|
||||
namespace Sleepy\types;
|
||||
|
||||
use Sleepy\core\aType;
|
||||
use Symfony\Component\Yaml as YML;
|
||||
|
||||
/**
|
||||
* Class for YAML output
|
||||
*/
|
||||
class YAML extends aType {
|
||||
|
||||
/**
|
||||
* The mime type for output
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $mime = 'text/yaml';
|
||||
|
||||
/**
|
||||
* Convert the data into the output format
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return string
|
||||
*/
|
||||
public function serialize($data = null)
|
||||
{
|
||||
if ( ! is_null($data))
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
else
|
||||
{
|
||||
$data = $this->data;
|
||||
}
|
||||
|
||||
// Yaml class doesn't support objects, cast them to arrays
|
||||
$data = (array) $data;
|
||||
|
||||
return YML::dump($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the encoded data to a native format
|
||||
*
|
||||
* @param string $data_string
|
||||
* @return object
|
||||
*/
|
||||
public function unserialize($data_string)
|
||||
{
|
||||
return YML::parse($data_string, FALSE, TRUE);
|
||||
}
|
||||
}
|
||||
// End of types/YAML.php
|
28
application/config/general.php
Executable file
28
application/config/general.php
Executable file
@ -0,0 +1,28 @@
|
||||
<?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
|
||||
* @package Sleepy
|
||||
*/
|
||||
|
||||
$config = [
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// The default format of data to be sent. Can be overwritten in controllers
|
||||
// --------------------------------------------------------------------------
|
||||
'default_output_format' => 'json',
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// The default format of data recieved. Can be form data or one of the
|
||||
// types defined in either the application/types or Sleepy/types folders
|
||||
// --------------------------------------------------------------------------
|
||||
'default_input_format' => 'json',
|
||||
|
||||
];
|
||||
|
||||
// End of config/general.php
|
23
application/config/routes.php
Executable file
23
application/config/routes.php
Executable file
@ -0,0 +1,23 @@
|
||||
<?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
|
||||
* @package Sleepy
|
||||
*/
|
||||
|
||||
$routes = [
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// The index controller, called if not specified in the routes or url
|
||||
// --------------------------------------------------------------------------
|
||||
'default_controller' => 'index',
|
||||
|
||||
|
||||
];
|
||||
|
||||
// End of config/routes.php
|
0
application/types/index.html
Executable file
0
application/types/index.html
Executable file
14
composer.json
Executable file
14
composer.json
Executable file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"authors": [{
|
||||
"name": "Timothy J. Warren",
|
||||
"email": "tim@timshomepage.net",
|
||||
"homepage": "https://timshomepage.net",
|
||||
"role": "Developer"
|
||||
}],
|
||||
"require-dev": {
|
||||
"phpunit/phpunit":"3.7.*"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
}
|
||||
}
|
55
index.php
Executable file
55
index.php
Executable file
@ -0,0 +1,55 @@
|
||||
<?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
|
||||
* @package Sleepy
|
||||
*/
|
||||
|
||||
namespace Sleepy;
|
||||
|
||||
define('BASEPATH', __DIR__ . '/');
|
||||
|
||||
define('APPPATH', __DIR__ . '/application/');
|
||||
|
||||
\spl_autoload_register(function($item) {
|
||||
$path_items = \explode('\\', \ltrim($item, '\\'));
|
||||
|
||||
// If the namespace is a straight mapping to the class, just load it
|
||||
$simple_path = \implode('/', $path_items);
|
||||
$file = BASEPATH . "{$simple_path}.php";
|
||||
if (file_exists($file))
|
||||
{
|
||||
require_once($file);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the application folder
|
||||
$file = str_replace("Sleepy", 'application', $file);
|
||||
if (file_exists($file))
|
||||
{
|
||||
require_once($file);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
$i = new \Sleepy\Core\Input();
|
||||
$o = new \Sleepy\Core\Output($i);
|
||||
$browser = \get_browser(NULL);
|
||||
$browser->browser_name_regex = utf8_encode($browser->browser_name_regex);
|
||||
$o->set_data(['json','html'], [
|
||||
'$_SERVER' => $i->server(),
|
||||
'$_GET' => $i->get(),
|
||||
'$_POST' => $i->post(),
|
||||
'$_ENV' => $i->env(),
|
||||
'$_COOKIE' => $i->cookie(),
|
||||
'browser' => $browser,
|
||||
'raw headers' => $i->header(),
|
||||
'parsed headers' => $i->header_array()
|
||||
]);
|
||||
|
||||
// End of index.php
|
22
phpdoc.dist.xml
Executable file
22
phpdoc.dist.xml
Executable file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<phpdoc>
|
||||
<title>Sleepy</title>
|
||||
<parser>
|
||||
<target>docs</target>
|
||||
</parser>
|
||||
<transformer>
|
||||
<target>docs</target>
|
||||
</transformer>
|
||||
<transformations>
|
||||
<template name="responsive" />
|
||||
</transformations>
|
||||
<files>
|
||||
<directory>.</directory>
|
||||
<directory>tests</directory>
|
||||
<directory>application</directory>
|
||||
<directory>vendor</directory>
|
||||
<ignore>tests/*</ignore>
|
||||
<ignore>application/*</ignore>
|
||||
<ignore>vendor/*</ignore>
|
||||
</files>
|
||||
</phpdoc>
|
32
tests/Bootstrap.php
Executable file
32
tests/Bootstrap.php
Executable file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
define('BASEPATH', realpath('../Sleepy/').'/');
|
||||
define('SPATH', realpath('../').'/');
|
||||
|
||||
/**
|
||||
* Autoloader for Sleepy
|
||||
*/
|
||||
\spl_autoload_register(function($item) {
|
||||
$path_items = \explode('\\', \ltrim($item, '\\'));
|
||||
|
||||
// If the namespace is a straight mapping to the class, just load it
|
||||
$simple_path = \implode('/', $path_items);
|
||||
$file = SPATH . "{$simple_path}.php";
|
||||
if (file_exists($file))
|
||||
{
|
||||
require_once($file);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
class Sleepy_TestCase extends PHPUnit_Framework_TestCase {
|
||||
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
if ( ! isset($_SERVER['REQUEST_METHOD']))
|
||||
{
|
||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||
}
|
||||
}
|
||||
}
|
189
tests/Core/InputTest.php
Executable file
189
tests/Core/InputTest.php
Executable file
@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
require_once(BASEPATH . 'core/Input.php');
|
||||
|
||||
class InputTest extends Sleepy_TestCase {
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Set some http headers as test items
|
||||
$_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36 OPR/18.0.1284.49';
|
||||
$_SERVER['HTTP_ACCEPT'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
|
||||
$_SERVER['HTTP_COOKIE'] = 'thp_tcms_session_id=9h4nvk15tjjegbeg8uvejncc9blkd81m';
|
||||
$_SERVER['HTTP_HOST'] = 'www.example.com';
|
||||
$_SERVER['HTTP_FOO'] = 'foo,bar';
|
||||
|
||||
// Set test $_COOKIE array
|
||||
$_COOKIE = [
|
||||
'thp_tcms_session_id' => '9h4nvk15tjjegbeg8uvejncc9blkd81m'
|
||||
];
|
||||
|
||||
// Set test $_GET array
|
||||
$_GET = [
|
||||
'foo' => 'bar',
|
||||
'baz' => 'foobar'
|
||||
];
|
||||
|
||||
// Set test $_POST array
|
||||
$_POST = [
|
||||
'this' => 'that',
|
||||
'tisket' => 'tasket'
|
||||
];
|
||||
|
||||
$this->input = new Sleepy\core\Input();
|
||||
}
|
||||
|
||||
public function dataTestHeader()
|
||||
{
|
||||
return [
|
||||
'all' => [
|
||||
'index' => NULL,
|
||||
'expected' => [
|
||||
'user_agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36 OPR/18.0.1284.49',
|
||||
'accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'cookie' => 'thp_tcms_session_id=9h4nvk15tjjegbeg8uvejncc9blkd81m',
|
||||
'host' => 'www.example.com',
|
||||
'foo' => 'foo,bar'
|
||||
]
|
||||
],
|
||||
'index' => [
|
||||
'index' => 'cookie',
|
||||
'expected' => 'thp_tcms_session_id=9h4nvk15tjjegbeg8uvejncc9blkd81m'
|
||||
],
|
||||
'invalid' => [
|
||||
'index' => 'boo',
|
||||
'expected' => NULL
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function dataHeaderArray()
|
||||
{
|
||||
return [
|
||||
'all' => [
|
||||
'index' => NULL,
|
||||
'expected' => [
|
||||
'user_agent' => [
|
||||
'versions' => [
|
||||
'Mozilla' => '5.0',
|
||||
'AppleWebKit' => '537.36',
|
||||
'Chrome' => '31.0.1650.57',
|
||||
'Safari' => '537.36',
|
||||
'OPR' => '18.0.1284.49',
|
||||
],
|
||||
'os' => [
|
||||
'Macintosh',
|
||||
'Intel Mac OS X 10_9_0',
|
||||
],
|
||||
'misc' => 'KHTML, like Gecko'
|
||||
],
|
||||
'accept' => [
|
||||
'text/html',
|
||||
'application/xhtml+xml',
|
||||
['application/xml', 'q=0.9'],
|
||||
['*/*', 'q=0.8']
|
||||
],
|
||||
'cookie' => [
|
||||
'thp_tcms_session_id' => '9h4nvk15tjjegbeg8uvejncc9blkd81m'
|
||||
],
|
||||
'host' => 'www.example.com',
|
||||
'foo' => [
|
||||
'foo',
|
||||
'bar'
|
||||
]
|
||||
]
|
||||
],
|
||||
'index' => [
|
||||
'index' => 'cookie',
|
||||
'expected' => [
|
||||
'thp_tcms_session_id' => '9h4nvk15tjjegbeg8uvejncc9blkd81m'
|
||||
]
|
||||
],
|
||||
'invalid' => [
|
||||
'index' => 'red',
|
||||
'expected' => NULL
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function dataTestPost()
|
||||
{
|
||||
return [
|
||||
'all' => [
|
||||
'index' => NULL,
|
||||
'expected' => [
|
||||
'this' => 'that',
|
||||
'tisket' => 'tasket'
|
||||
]
|
||||
],
|
||||
'index' => [
|
||||
'index' => 'this',
|
||||
'expected' => 'that'
|
||||
],
|
||||
'invalid' => [
|
||||
'index' => 'puppies',
|
||||
'expected' => NULL
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function dataTestGet()
|
||||
{
|
||||
return [
|
||||
'all' => [
|
||||
'index' => NULL,
|
||||
'expected' => [
|
||||
'foo' => 'bar',
|
||||
'baz' => 'foobar'
|
||||
]
|
||||
],
|
||||
'index' => [
|
||||
'index' => 'foo',
|
||||
'expected' => 'bar'
|
||||
],
|
||||
'invalid' => [
|
||||
'index' => 'applesauce',
|
||||
'expected' => NULL
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataTestHeader
|
||||
*/
|
||||
public function testHeader($index, $expected)
|
||||
{
|
||||
$res = $this->input->header($index);
|
||||
$this->assertEquals($expected, $res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataHeaderArray
|
||||
*/
|
||||
public function testHeaderArray($index, $expected)
|
||||
{
|
||||
$result = $this->input->header_array($index);
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataTestPost
|
||||
*/
|
||||
public function testPost($index, $expected)
|
||||
{
|
||||
$result = $this->input->post($index);
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataTestGet
|
||||
*/
|
||||
public function testGet($index, $expected)
|
||||
{
|
||||
$result = $this->input->get($index);
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
}
|
||||
// End of InputTest.php
|
17
tests/Core/aTypeTest.php
Executable file
17
tests/Core/aTypeTest.php
Executable file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
require_once(BASEPATH . 'core/iType.php');
|
||||
require_once(BASEPATH . 'core/aType.php');
|
||||
|
||||
use Sleepy\core\iType;
|
||||
use Sleepy\core\aType;
|
||||
|
||||
class aTypeTest extends Sleepy_TestCase {
|
||||
|
||||
public function testSanity()
|
||||
{
|
||||
$this->assertEquals(['Sleepy\\core\\iType' => 'Sleepy\\core\\iType'], class_implements('Sleepy\\core\\aType'));
|
||||
}
|
||||
|
||||
}
|
||||
// End of aTypeTest.php
|
41
tests/Type/JSONTest.php
Executable file
41
tests/Type/JSONTest.php
Executable file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
require_once(BASEPATH . 'core/iType.php');
|
||||
require_once(BASEPATH . 'core/aType.php');
|
||||
require_once(BASEPATH . 'types/JSON.php');
|
||||
|
||||
use Sleepy\core;
|
||||
use Sleepy\types\JSON;
|
||||
use Sleepy\execeptions;
|
||||
|
||||
class MockJSON extends JSON {
|
||||
|
||||
protected $mime = '';
|
||||
}
|
||||
|
||||
class JSONTest extends Sleepy_Testcase {
|
||||
|
||||
public function setUp() {
|
||||
$this->JSON = new JSON([]);
|
||||
}
|
||||
|
||||
public function testSanity() {
|
||||
$this->assertTrue(is_a($this->JSON, 'Sleepy\\types\\JSON'));
|
||||
$this->assertTrue(is_a($this->JSON, 'Sleepy\\core\\aType'));
|
||||
$this->assertEquals(['Sleepy\\core\\iType' => 'Sleepy\\core\\iType'], class_implements('Sleepy\\types\\JSON'));
|
||||
}
|
||||
|
||||
public function testFunction()
|
||||
|
||||
|
||||
public function testNIE() {
|
||||
try {
|
||||
$json = new MockJSON([]);
|
||||
}
|
||||
catch (Sleepy\exceptions\NotImplementedException $e) {
|
||||
$this->assertTrue(TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// End of JSONTest
|
26
tests/phpunit.xml
Executable file
26
tests/phpunit.xml
Executable file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit
|
||||
bootstrap="./Bootstrap.php"
|
||||
colors="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
stopOnError="false"
|
||||
stopOnFailure="false"
|
||||
stopOnIncomplete="false"
|
||||
stopOnSkipped="false">
|
||||
<testsuites>
|
||||
<testsuite name="Sleepy Core Test Suite">
|
||||
<directory suffix="Test.php">./core</directory>
|
||||
<directory suffix="Test.php">./exceptions</directory>
|
||||
<directory suffix="Test.php">./types</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<blacklist>
|
||||
<directory suffix=".php">PEAR_INSTALL_DIR</directory>
|
||||
<directory suffix=".php">PHP_LIBDIR</directory>
|
||||
<directory suffix=".php">../vendor</directory>
|
||||
</blacklist>
|
||||
</filter>
|
||||
</phpunit>
|
Reference in New Issue
Block a user