<?php
/**
* REST
* Author: Robert B Gottier
* License: BSD 3-Clause
*
* The goal of this simple class is to make available the
* necessary information and data related to REST APIs.
*
# -------------------------
# Basic example usage
$REST = new REST;
if( $REST->method == 'put' )
$data = $REST->input_data;
# -------------------------
# -------------------------
# Example allowing POST as PATCH
# overridden by $_POST['_method'] = 'PATCH'
$REST = new REST([
'_override_by_post_var' => TRUE
]);
if( $REST->method == 'patch' )
$data = $REST->input_data;
# -------------------------
# -------------------------
# Example allowing POST as PUT
# overridden by X-HTTP-Method-Override header = 'PUT'
$REST = new REST([
'_override_by_header' => TRUE
]);
if( $REST->method == 'put' )
$data = $REST->input_data;
# -------------------------
*
* Note: no attempt has been made to validate or sanitize
* the data available in $REST->input_data.
*/
class REST {
/**
* The detected HTTP request method
*/
public $method = '';
/**
* The detected content type
*/
public $content_type = '';
/**
* Input data from PUT and PATCH requests
*/
public $input_data = [];
/**
* Allowed HTTP methods
*/
protected $_allowed_methods = ['get','delete','post','put','options','patch','head'];
/**
* Overridable HTTP methods
*/
protected $_overridable_methods = ['post'];
/**
* Replacement HTTP methods
*/
protected $_replacement_methods = ['put','patch'];
/**
* Allow method overrides by post variable
*/
protected $_override_by_post_var = FALSE;
/**
* Allow method overrides by header
*/
protected $_override_by_header = FALSE;
/**
* The request headers
*/
protected $_headers = [];
/**
* php://input
*/
protected $_php_input = NULL;
// -----------------------------------------------------------------------
/**
* Class constructor
* -----------------
*
* @param array class configuration
*/
public function __construct( $params = [] )
{
// Any key that is a REST class property can be configured.
foreach( $params as $key => $value )
{
if( property_exists( $this, $key ) )
$this->$key = $value;
}
/**
* CLI indicates that we are doing tests through phpunit.
* Run automatically if we are not doing tests.
*/
if( ! ( PHP_SAPI === 'cli' OR defined('STDIN') ) )
$this->run();
}
// -----------------------------------------------------------------------
/**
* Get all headers, detect method, detect content type, and get input data.
*/
public function run()
{
$this->_headers = $this->_get_request_headers();
$this->method = $this->_detect_method();
$this->content_type = $this->_detect_content_type();
$this->input_data = $this->_fetch_input_data();
}
// -----------------------------------------------------------------------
/**
* Get the request headers
*/
protected function _get_request_headers()
{
// For whatever reason, getallheaders wasn't available when testing
if( function_exists('getallheaders') )
return getallheaders();
return NULL;
}
// -----------------------------------------------------------------------
/**
* Detect the request method
* -------------------------
* A put or patch request could come from an HTML form,
* but it would need to use POST as the request method,
* so we may allow the post var "_method" to indicate that
* we're doing a post that should be seen as a put or patch.
*
* We may also allow the X-HTTP-Method-Override header to
* change the request method to put or patch.
*
* If the override is not allowed, the method is not changed.
*/
protected function _detect_method()
{
$method = isset( $_SERVER['REQUEST_METHOD'] )
? strtolower( $_SERVER['REQUEST_METHOD'] )
: NULL;
// Method override ?
if( ! is_null( $method ) && in_array( $method, $this->_overridable_methods ) )
{
// Method override by header
if(
isset( $this->_headers['X-HTTP-Method-Override'] ) &&
in_array( strtolower( $this->_headers['X-HTTP-Method-Override'] ), $this->_replacement_methods )
){
$method = $this->_override_by_header
? strtolower( $this->_headers['X-HTTP-Method-Override'] )
: $method;
}
// Method override by POST var
else if(
isset( $_POST['_method'] ) &&
in_array( strtolower( $_POST['_method'] ), $this->_replacement_methods )
){
$method = $this->_override_by_post_var
? strtolower( $_POST['_method'] )
: $method;
}
}
return in_array( $method, $this->_allowed_methods )
? $method
: NULL;
}
// -----------------------------------------------------------------------
/**
* Try to find the declared mime type for the request
*/
protected function _detect_content_type()
{
if( isset( $_SERVER['CONTENT_TYPE'] ) )
{
$type = $_SERVER['CONTENT_TYPE'];
if( ! empty( $type ) )
{
// Only the first part of the header is the mime type
return strpos( $type, ';' ) !== FALSE
? current( explode( ';', $type ) )
: $type;
}
}
return NULL;
}
// -----------------------------------------------------------------------
/**
* For put or patch requests, the input data will be in php://input,
* unless this is a POST disguised as a PUT or PATCH.
*/
protected function _fetch_input_data()
{
// If there's a post array, just return it
if( in_array( $this->method, ['put','patch'] ) && ! empty( $_POST ) )
return $_POST;
// Otherwise, check PHP's input stream
if( in_array( $this->method, ['put','patch'] ) )
{
$input = $this->_cache_php_input();
if( ! empty( $input ) )
return $this->_process_php_input();
}
return NULL;
}
// -----------------------------------------------------------------------
/**
* Cache contents of php://input
*/
protected function _cache_php_input()
{
! is_null( $this->_php_input ) OR
$this->_php_input = file_get_contents('php://input');
return $this->_php_input;
}
// -----------------------------------------------------------------------
/**
* Parse, decode, unserialize, or otherwise process the php input.
*/
protected function _process_php_input()
{
switch( $this->content_type )
{
case 'application/x-www-form-urlencoded':
parse_str( $this->_php_input , $vars );
break;
case 'application/json':
$vars = json_decode( $this->_php_input, TRUE);
break;
case 'application/vnd.php.serialized':
$vars = unserialize( $this->_php_input );
break;
default:
$vars = $this->_php_input;
}
return $vars;
}
// -----------------------------------------------------------------------
}
/* End of file REST.php */