Last active
August 12, 2016 10:47
-
-
Save SplittyDev/c67bbdf577426d3ee190 to your computer and use it in GitHub Desktop.
ezrest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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