Skip to content

Instantly share code, notes, and snippets.

@SplittyDev
Last active August 12, 2016 10:47
Show Gist options
  • Save SplittyDev/c67bbdf577426d3ee190 to your computer and use it in GitHub Desktop.
Save SplittyDev/c67bbdf577426d3ee190 to your computer and use it in GitHub Desktop.
ezrest
<?php
// Predefined actions
function emit_empty_200 ($params) {
// 200 OK
http_response_code (200);
return array ();
}
function emit_empty_404 ($params) {
// 404 Not Found
http_response_code (404);
return array ();
}
/**
* ezrest entry
*/
class ezrest_entry {
public $entry;
public $params;
/**
* Constructor
* @param string $entry
* @param array $params
*/
function __construct ($entry, $params) {
$this -> entry = $entry;
$this -> params = $params;
}
}
class ezrest {
// Deserialized JSON config
static $config;
/**
* Do all the work and invoke the appropriate actions
* @return bool true if everything succeeded; false, otherwise
*/
public static function work () {
// Get the corrected request uri
$request = self::correct_request_uri ();
// Proceed if the request uri could be corrected; return, otherwise
if ($request !== false) {
// Require the specified script that contains the user-defined actions
@require_once (self::$config["ezrest"]["actions_path"]);
// Parse the request uri
$chain = self::parse_request ($request);
// Parse the request chain
$config = self::parse_chain ($chain);
// Invoke the appropriate action
$result = self::call_action ($config);
if ($result === false)
// No matching action could be found
return false;
// Serialize and print the action result
self::serialize_output ($result);
// Assume that everything worked
return true;
}
// The request uri couldn't be corrected
else return false;
// Assume that everything worked
return true;
}
/**
* Bring the request uri into a form that's easily processable
* @return string corrected request uri
*/
static function correct_request_uri () {
// Get the original request uri
$request = $_SERVER["REQUEST_URI"];
// Find the position of the base path in the request uri
$base_url_pos = strpos ($request, self::$config["ezrest"]["base_path"]);
// Proceed if the base path could be found in the request uri
if ($base_url_pos !== false) {
// Skip the base path
$base_url_pos += strlen (self::$config["ezrest"]["base_path"]);
// Skip a trailing slash if needed
if (strpos (self::$config["ezrest"]["base_path"], '/') === false)
$base_url_pos += 1;
// Get the request string without the base path
$request = substr ($request, $base_url_pos);
// Add a trailing slash if needed
if ($request[strlen ($request) - 1] !== '/')
$request .= '/';
}
// The base path couldn't be found in the request uri
else return false;
// Assume that everything worked
return $request;
}
/**
* Parse the corrected request string
* @param string $request corrected request string
* @return array request chain
*/
static function parse_request ($request) {
$chain = array ();
$tmp = $request;
$entry;
$i = 0;
// Loop while there are still uri parts available
while (strpos ($tmp, '/') !== false) {
$tmp;
// Set the first entry if it isn't set by now
if (!isset ($entry)) {
$entry = substr ($request, 0, strpos ($tmp, '/'));
$tmp = substr ($tmp, strlen ($entry) + 1);
}
// Get the next uri part
else {
$entry = substr ($tmp, 0, strpos ($tmp, '/'));
$tmp = substr ($tmp, strlen ($entry) + 1);
}
$real_entry = $entry;
// Collect the GET parameters in the current uri part, if any
$params = self::parse_request_params ($entry);
if (strpos ($entry, '?') !== false)
$real_entry = substr ($entry, 0, strpos ($entry, '?'));
// Add the current entry (with stripped GET parameters) to the chain
$chain[] = new ezrest_entry ($real_entry, $params);
}
// Return the chain
return $chain;
}
/**
* Collect all GET parameters in the current uri part
* @param string $part the current uri part
* @return array associative array consisting of all GET parameters
*/
static function parse_request_params ($part) {
$params = array ();
$buf = "";
$key;
$chr;
// Check if the uri contains GET parameters
$start = strpos ($part, '?');
// If no, return an empty array
if ($start === false)
return array ();
// Increment the start position by one to skip the '?' character
$start += 1;
// Loop over every character in the uri
for ($pos = $start; $pos <= strlen ($part); $pos += 1) {
// Check if we have reached the end
// If so, add the current key-value-pair to the params array
if ($pos == strlen ($part)) {
$params[$key] = $buf;
break;
}
// Get the current character
$chr = $part[$pos];
// Check if we are done with parsing a key
if ($chr == '=') {
$key = $buf;
$buf = "";
}
// Check if we are done with parsing a value
elseif ($chr == '&') {
$params[$key] = $buf;
$buf = "";
}
// Add the current character to the buffer
// if the current char was neither the end of a key
// nor the end of a value
else {
$buf .= $chr;
}
}
return $params;
}
/**
* Process the request chain
* @param array $chain the request chain
* @return array the final request configuration
*/
static function parse_chain ($chain) {
$params = array ();
$config_entries = self::$config["ezrest"]["entries"];
// Return an empty configuration if the chain is empty
if (count ($chain) == 0)
return array ("params" => array ());
// Initialize the current entry
$current_entry = $chain[0];
// Return an empty configuration if the first entry doesn't contain
// any sub-entries
if (!array_key_exists ($current_entry -> entry, $config_entries))
return array ("params" => array ());
// Initialize the current request configuration
$current_config_arr =
$config_entries[$current_entry -> entry];
// Loop over every request part in the chain
for ($pos = 1; $pos < count ($chain); $pos += 1) {
// Loop over every parameter and add it to the params array
// Override any previously set parameters with the same name
// The higher the position of the current request in the request chain,
// the higher the precedence of the parameter
foreach ($current_entry -> params as $key => $value)
$params[$key] = $value;
// Update the current entry
$current_entry = $chain[$pos];
// Check if the current entry could contain sub-entries
if (array_key_exists ("entries", $current_config_arr)) {
$tmp = $current_config_arr["entries"];
// Check if the current entry contains a named sub-entry
if (array_key_exists ($current_entry -> entry, $tmp)) {
$current_config_arr = $tmp[$current_entry -> entry];
}
// Check if the current entry contains a wildcard sub-entry
elseif (array_key_exists ("*", $tmp)) {
$current_config_arr = $tmp["*"];
}
// Break if no sub-entries were found
else break;
// Get the named parameter name of the current sub-entry
$urlparam = self::resolve_param_name
($current_entry, $current_config_arr);
// Add the named parameter to the params array or do nothing if
// the current sub-entry is not named
if ($urlparam !== false)
$params[$urlparam[0]] = $urlparam[1];
}
}
// Add the parameters of the last entry to the params array
foreach ($current_entry -> params as $key => $value)
$params[$key] = $value;
// Update and return the final request configuration
$current_config_arr["params"] = $params;
return $current_config_arr;
}
/**
* Resolve the named parameter key-value-pair of the current entry
* @param ezrest_entry $current_entry
* @param array $current_chain
* @return array|bool the resolved key-value-pair or false if the current
* entry is not a named parameter
*/
static function resolve_param_name ($current_entry, $current_chain) {
// Check if the current entry is named and return an array containing
// the key and the value
if (array_key_exists ("name", $current_chain)) {
return array ($current_chain["name"], $current_entry -> entry);
}
// The current entry is not a named parameter
return false;
}
/**
* Invoke the appropriate actions
* @param array $config the final request configuration
* @return bool true if the operation succeeded; false, otherwise
*/
static function call_action ($config) {
// Get the request method
$method = $_SERVER["REQUEST_METHOD"];
// Check if an action could be defined
if (array_key_exists ("actions", $config)) {
// Check if there is one or more action for the specified request method
if (array_key_exists ($method, $config["actions"])) {
$action =
call_user_func ($config["actions"][$method], $config["params"]);
return $action;
}
// No actions match the specified request method; check if there is
// a default action specified
elseif (array_key_exists ("DEFAULT", $config["actions"])) {
$action =
call_user_func ($config["actions"]["DEFAULT"], $config["params"]);
return $action;
}
// No actions are specified; call the globally defined default action
else {
return self::call_default_action ($config);
}
}
// No actions are specified; call the globally defined default action
else {
return self::call_default_action ($config);
}
// Assume that everything worked
return true;
}
/**
* Invoke the appropriate default action
* @param array $config the final request configuration
* @return bool true if the operation succeeded; false, otherwise
*/
static function call_default_action ($config) {
// Get the request method
$method = $_SERVER["REQUEST_METHOD"];
// Check if any default actions are globally defined and return false
// if not
if (!array_key_exists ("default_actions", self::$config["ezrest"]))
return false;
// Get the globally defined default actions
$default_actions = self::$config["ezrest"]["default_actions"];
// Check if any default actions match the request method
if (array_key_exists ($method, $default_actions))
return call_user_func ($default_actions[$method], $config["params"]);
// Assume that everything worked
return true;
}
static function serialize_output ($result) {
$accept;
$output;
if (isset ($_SERVER['HTTP_ACCEPT']))
$accept = $_SERVER['HTTP_ACCEPT'];
else $accept = 'application/json';
switch ($accept) {
case 'application/json':
$output = json_encode ($result);
break;
case 'application/xml':
$root = new SimpleXMLElement ('<root/>');
$output = self::array_to_xml ($result, $root) -> asXML ();
break;
default:
$output = json_encode ($result);
break;
}
echo $output;
}
static function array_to_xml ($arr, $xml) {
foreach ($arr as $k => $v) {
if (is_numeric ($k))
$k = "i".$k;
is_array ($v)
? self::array_to_xml ($v, $xml -> addChild ($k))
: $xml -> addChild($k, $v);
}
return $xml;
}
/**
* Load a JSON-formatted configuration file
* @param string $file path to the configuration file
*/
public static function load ($file) {
self::$config = json_decode (file_get_contents ($file), true);
}
/**
* Create a JSON-formatted configuration file
* @param string $file path to the configuration file that's going to be
* created
*/
public static function create ($file) {
// An array representing a basic example configuration
$default_json = array (
'ezrest' => array (
'base_path' => 'base/path/of/your/project/',
'action_path' => 'actions.php',
'accept' => array (
'application/json',
'application/xml'
),
'default_actions' => array (
'GET' => 'emit_empty_response',
'PUT' => 'emit_empty_response',
'POST' => 'emit_empty_response',
'DELETE' => 'emit_empty_response'
),
'entries' => array (
'api' => array (
'entries' => array (
'v1' => array (
'entries' => array (
'users' => array (
'actions' => array (
'GET' => array ('list_users')
)))))))));
// Check if the file already exists
if (!file_exists ($file)) {
// Create the file or update its timestamp
touch ($file);
// Get the JSON-formatted representation of the example configuration
$json = json_encode ($default_json);
// Write the JSON-formatted configuration to the file
file_put_contents ($file, $json);
}
}
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment