Skip to content

Commit

Permalink
changed project name to presto, added variable handling in URI, added…
Browse files Browse the repository at this point in the history
… filetype detection
  • Loading branch information
Jake McGraw committed Aug 24, 2011
1 parent b0b7d51 commit 4cdc279
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 32 deletions.
33 changes: 29 additions & 4 deletions README.md
@@ -1,9 +1,34 @@
## mapd: Map HTTP requests to PHP functions
## presto: A micro framework for mapping HTTP requests to PHP function calls

### Example

GET /foo/bar => mapd_get_foo_bar();
<?php
// HTTP GET / maps to
presto_get_index_index();
// HTTP GET /foo maps to
presto_get_foo_index();
// HTTP GET /foo/bar maps to
presto_get_foo_bar();
// HTTP GET /foo/bar-bar maps to
presto_get_foo_barBar();
GET /foo/bar-spam => mapd_get_foo_barSpam();
// HTTP POST /foo/bar maps to
presto_post_foo_bar(array(/* post variables */));
// HTTP GET /foo/bar/12345 maps to
presto_get_foo_bar(array(12345));
// HTTP POST /foo/bar/hello maps to
presto_get_foo_bar(array("hello", /* post variables */));
// HTTP GET /foo/bar/hello/world/monkey maps to
presto_get_foo_bar(array("hello" => "world", "monkey"));

POST /foo/bar => mapd_post_foo_bar($post_vars);
## Install

TODO

128 changes: 108 additions & 20 deletions app/mapd.php → app/presto.php
Expand Up @@ -6,51 +6,107 @@
* @param string $method HTTP request method (head, get, post, put, delete)
* @param string $request HTTP path
* @param string $base_url Prefix HTTP paths (optional)
* @return mixed Function name on success, FALSE on error
* @return mixed [presto function, variables] on success, FALSE on error
* @author Jake McGraw <social@jakemcgraw.com>
*/
function mapd_route($method, $request, $base_url=null)
function presto_route($method, $request, $base_url=null)
{
$allowed_methods = array("get", "post", "put", "delete", "head");
$method = strtolower($method);

if (false === in_array($method, $allowed_methods)) {
return false;
return array(false, array());
}

$orig_request = $request;

// adios get parameters
if (strpos($request, "?") !== false) {
list($request) = explode("?", $request);
}

// goodbye base url
$request = (null !== $base_url) ?
preg_replace('{^'.$base_url.'}i', "", $request) : $request;

$parts = explode("/", strtolower($request));
$parts = array_filter($parts, function($n){
return !empty($n);
});

if (empty($parts)) {
return "mapd_{$method}_index";
// see ya empty paths
$parts = array_values(array_filter($parts, function($n){
return !empty($n);
}));

$vars = array();

// process uri parameters
if (count($parts) > 2) {
$tmp = array_slice($parts, 2);
$cnt = count($tmp);
for ($i=0; $i<$cnt; $i++) {
if ($i == $cnt-1) {
$vars[] = $tmp[$i];
}
else {
$vars[$tmp[$i]] = $tmp[++$i];
}
}
// get rid of uri parameters from $parts, now in $vars
$parts = array_slice($parts, 0, 2);
}
// not long enough for uri parameters, inject index
else if (count($parts) < 2) {
do {
$parts[] = "index";
}
while(count($parts) < 2);
}

// convert "foo-bar" to "fooBar"
$parts = array_map(function($n) {
return preg_replace_callback('/-(\w)/', function($m){
return strtoupper($m[1]);
}, $n);
}, $parts);

array_unshift($parts, "mapd", $method);
return implode("_", $parts);
// last variable is allowed to indicate filetype
if (!empty($vars)) {
$vals = array_values($vars);
$last = $vals[count($vals)-1];
}
else {
$last = $parts[1];
$parts[1] = preg_replace('/\.[^.]+$/', "", $last);
}

// check for filetype
if (false !== strpos($last, ".")) {
if (preg_match("/\.(\w+)$/", $last, $match)) {
$vars["_filetype"] = $match[1];
}
}

// remove invalid characters
$parts = array_map(function($n) {
return preg_replace('/[^A-Za-z0-9]/', "", $n);
}, $parts);

array_unshift($parts, "presto", $method);

// generate function name
$func = implode("_", $parts);

return array($func, $vars);
}

/**
* Executes a function based on URI, HTTP request method
*
* @param string $func Function to execute, provided by mapd_route()
* @param array $vars Parameters to pass to mapd function (optional)
* @param string $func Function to execute, provided by presto_route()
* @param array $vars Parameters to pass to presto function (optional)
* @return mixed String or array on success, FALSE on error
* @author Jake McGraw <social@jakemcgraw.com>
*/
function mapd_exec($func, array $vars=array())
function presto_exec($func, array $vars=array())
{
if (false === $func || !is_string($func)) {
return array(false, array(
Expand All @@ -76,15 +132,15 @@ function mapd_exec($func, array $vars=array())
/**
* Executes a function based on URI, HTTP request, sends JSON response
*
* @param string $func Function to execute, provided by mapd_route()
* @param array $vars Parameters to pass to mapd function (optional)
* @param string $func Function to execute, provided by presto_route()
* @param array $vars Parameters to pass to presto function (optional)
* @return mixed String or array on success, FALSE on error
* @author Jake McGraw <social@jakemcgraw.com>
*/
function mapd_exec_json($func, array $vars=array())
function presto_exec_json($func, array $vars=array())
{
ob_start();
list($success, $result) = mapd_exec($func, $vars);
list($success, $result) = presto_exec($func, $vars);
$output = ob_get_clean();

$response = array();
Expand All @@ -108,24 +164,56 @@ function mapd_exec_json($func, array $vars=array())
if ($success && (is_string($result) || is_array($result))) {
$response["result"] = $result;
}

$json = "";

// try to encode response
if (false === ($json = @json_encode($response))) {
if (!empty($response) && (false === ($json = @json_encode($response)))) {

// error encoding response
if ($success) {
header("HTTP/1.0 500 Internal Server Error");
}

$response = array(
"error" => "Unable to encode API response",
"errno" => 500 + ((int) json_last_error())
);

$json = json_encode($response);
}

header("Content-type: application/json");
echo $json;

return $result;
}


function presto_exec_jsonp($func, array $vars=array())
{
/*
TODO
*/
}

function presto_exec_html($func, array $vars=array())
{
/*
TODO
*/
}

function presto_exec_xml($func, array $vars=array())
{
/*
TODO
*/
}

function presot_exec_txt($func, array $vars=array())
{
/*
TODO
*/
}
14 changes: 6 additions & 8 deletions public/index.php
@@ -1,13 +1,11 @@
<?php

require_once "../app/mapd.php";
require_once "../app/presto.php";
// require_once "../app/<YOUR PRESTO FUNCTIONS>.php";

mapd_exec_json(
mapd_route(
$_SERVER["REQUEST_METHOD"],
$_SERVER["REQUEST_URI"],
null
),
$_POST
list($func, $vars) = presto_route(
$_SERVER["REQUEST_METHOD"],
$_SERVER["REQUEST_URI"]
);

presto_exec_json($func, array_merge($_REQUEST, $vars));
109 changes: 109 additions & 0 deletions tests/PrestoTest.php
@@ -0,0 +1,109 @@
<?php

require_once "PHPUnit/Framework/TestCase.php";
require_once "../app/presto.php";

class PrestoTest extends PHPUnit_Framework_TestCase
{
public function testRoute()
{
list($func) = presto_route("GET", "/");
$this->assertEquals("presto_get_index_index", $func);

list($func) = presto_route("GET", "////");
$this->assertEquals("presto_get_index_index", $func);

list($func) = presto_route("GET", "/foo");
$this->assertEquals("presto_get_foo_index", $func);

foreach(array("GET", "POST", "PUT", "DELETE", "HEAD") as $method) {
list($func) = presto_route($method, "/foo/bar");
$this->assertEquals("presto_" . strtolower($method) . "_foo_bar", $func);

list($func) = presto_route(strtolower($method), "/foo/bar");
$this->assertEquals("presto_" . strtolower($method) . "_foo_bar", $func);
}

list($func) = presto_route("GET", "/fOO/bAR");
$this->assertEquals("presto_get_foo_bar", $func);

list($func) = presto_route("GET", "/foo/bar?hello=world");
$this->assertEquals("presto_get_foo_bar", $func);

list($func) = presto_route("FAIL", "/foo/bar");
$this->assertFalse($func);

list($func) = presto_route("GET", "/foo!@#$%^&*()+=/bar.;:'\"/");
$this->assertEquals("presto_get_foo_bar", $func);
}

public function testRouteWithBaseUrl()
{
list($func) = presto_route("GET", "/api/", "/api");
$this->assertEquals("presto_get_index_index", $func);

list($func) = presto_route("GET", "/api/", "/api/");
$this->assertEquals("presto_get_index_index", $func);

list($func) = presto_route("GET", "/api/foo", "/api");
$this->assertEquals("presto_get_foo_index", $func);

list($func) = presto_route("GET", "/api/foo/bar", "/api");
$this->assertEquals("presto_get_foo_bar", $func);
}

public function testRouteVariables()
{
foreach(array("/", "/foo", "/foo/bar", "/foo/bar/", "/foo///bar//") as $uri) {
list($func, $vars) = presto_route("GET", $uri);
$this->assertEmpty($vars);
}

list($func, $vars) = presto_route("GET", "/foo/bar/hello");
$this->assertEquals("presto_get_foo_bar", $func);
$this->assertEquals(array("hello"), $vars);

list($func, $vars) = presto_route("GET", "/foo/bar/hello/");
$this->assertEquals("presto_get_foo_bar", $func);
$this->assertEquals(array("hello"), $vars);

list($func, $vars) = presto_route("GET", "/foo///bar///hello");
$this->assertEquals("presto_get_foo_bar", $func);
$this->assertEquals(array("hello"), $vars);

list($func, $vars) = presto_route("GET", "/foo/bar/hello/world");
$this->assertEquals("presto_get_foo_bar", $func);
$this->assertEquals(array("hello" => "world"), $vars);

list($func, $vars) = presto_route("GET", "/foo/bar/a/b/c");
$this->assertEquals("presto_get_foo_bar", $func);
$this->assertEquals(array("a" => "b", "c"), $vars);

list($func, $vars) = presto_route("GET", "/foo/bar/a/b/c/d");
$this->assertEquals("presto_get_foo_bar", $func);
$this->assertEquals(array("a" => "b", "c" => "d"), $vars);

list($func, $vars) = presto_route("GET", "/foo/bar/a/B/c/D");
$this->assertEquals("presto_get_foo_bar", $func);
$this->assertEquals(array("a" => "b", "c" => "d"), $vars);

list($func, $vars) = presto_route("GET", "/foo/bar/hello/world-spam");
$this->assertEquals("presto_get_foo_bar", $func);
$this->assertEquals(array("hello" => "world-spam"), $vars);
}

public function testRouteFiletype()
{
list($func, $vars) = presto_route("GET", "/foo/bar.js");
$this->assertEquals("presto_get_foo_bar", $func);
$this->assertEquals(array("_filetype" => "js"), $vars);

list($func, $vars) = presto_route("GET", "/foo/bar/hello.js");
$this->assertEquals("presto_get_foo_bar", $func);
$this->assertEquals(array("hello.js", "_filetype" => "js"), $vars);

list($func, $vars) = presto_route("GET", "/foo/bar/hello/world.js");
$this->assertEquals("presto_get_foo_bar", $func);
$this->assertEquals(array("hello" => "world.js", "_filetype" => "js"), $vars);
}
}

0 comments on commit 4cdc279

Please sign in to comment.