Skip to content

Commit

Permalink
Merge branch 'feature-resource-routing' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh Lockhart committed Aug 4, 2011
2 parents 0b3380f + a1cac97 commit 716144d
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 66 deletions.
49 changes: 49 additions & 0 deletions Slim/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ class Slim_Route {
*/
protected $params = array();

/**
* @var array HTTP methods supported by this Route
*/
protected $methods = array();

/**
* @var Slim_Router The Router to which this Route belongs
*/
Expand Down Expand Up @@ -184,6 +189,50 @@ public function getParams() {
return $this->params;
}

/**
* Add supported HTTP method(s)
* @return void
*/
public function setHttpMethods() {
$args = func_get_args();
$this->methods = $args;
}

/**
* Get supported HTTP methods
* @return array
*/
public function getHttpMethods() {
return $this->methods;
}

/**
* Append supported HTTP methods
* @return void
*/
public function appendHttpMethods() {
$args = func_get_args();
$this->methods = array_merge($this->methods, $args);
}

/**
* Append supported HTTP methods (alias for Route::appendHttpMethods)
* @return Slim_Route
*/
public function via() {
$args = func_get_args();
$this->methods = array_merge($this->methods, $args);
return $this;
}

/**
* Detect support for an HTTP method
* @return bool
*/
public function supportsHttpMethod( $method ) {
return in_array($method, $this->methods);
}

/**
* Get router
* @return Slim_Router
Expand Down
16 changes: 4 additions & 12 deletions Slim/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,7 @@ class Slim_Router {
*/
public function __construct( Slim_Http_Request $request ) {
$this->request = $request;
$this->routes = array(
'GET' => array(),
'POST' => array(),
'PUT' => array(),
'DELETE' => array()
);
$this->routes = array();
}

/**
Expand All @@ -112,8 +107,7 @@ public function setRequest( Slim_Http_Request $req ) {
public function getMatchedRoutes( $reload = false ) {
if ( $reload || is_null($this->matchedRoutes) ) {
$this->matchedRoutes = array();
$method = $this->request->isHead() ? Slim_Http_Request::METHOD_GET : $this->request->getMethod();
foreach ( $this->routes[$method] as $route ) {
foreach ( $this->routes as $route ) {
if ( $route->matches($this->request->getResourceUri()) ) {
$this->matchedRoutes[] = $route;
}
Expand All @@ -126,14 +120,12 @@ public function getMatchedRoutes( $reload = false ) {
* Map a route to a callback function
* @param string $pattern The URL pattern (ie. "/books/:id")
* @param mixed $callable Anything that returns TRUE for is_callable()
* @param string $method The HTTP request method (GET, POST, PUT, DELETE)
* @return Slim_Route
*/
public function map( $pattern, $callable, $method ) {
public function map( $pattern, $callable ) {
$route = new Slim_Route($pattern, $callable);
$route->setRouter($this);
$methodKey = ( $method === Slim_Http_Request::METHOD_HEAD ) ? Slim_Http_Request::METHOD_GET : $method;
$this->routes[$methodKey][] = $route;
$this->routes[] = $route;
return $route;
}

Expand Down
102 changes: 59 additions & 43 deletions Slim/Slim.php
Original file line number Diff line number Diff line change
Expand Up @@ -364,28 +364,37 @@ public function config( $name, $value = null ) {
*
* Slim::get('/foo'[, middleware, middleware, ...], callable);
*
* @param string The HTTP method (ie. GET, POST, PUT, DELETE)
* @param array See notes above
* @param array (See notes above)
* @return Slim_Route
*/
protected function mapRoute($type, $args) {
protected function mapRoute($args) {
$pattern = array_shift($args);
$callable = array_pop($args);
$route = $this->router->map($pattern, $callable, $type);
$route = $this->router->map($pattern, $callable);
if ( count($args) > 0 ) {
$route->setMiddleware($args);
}
return $route;
}

/**
* Add generic route without associated HTTP method
* @see Slim::mapRoute
* @return Slim_Route
*/
public function map() {
$args = func_get_args();
return $this->mapRoute($args);
}

/**
* Add GET route
* @see Slim::mapRoute
* @return Slim_Route
*/
public function get() {
$args = func_get_args();
return $this->mapRoute(Slim_Http_Request::METHOD_GET, $args);
return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_GET, Slim_Http_Request::METHOD_HEAD);
}

/**
Expand All @@ -395,7 +404,7 @@ public function get() {
*/
public function post() {
$args = func_get_args();
return $this->mapRoute(Slim_Http_Request::METHOD_POST, $args);
return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_POST);
}

/**
Expand All @@ -405,7 +414,7 @@ public function post() {
*/
public function put() {
$args = func_get_args();
return $this->mapRoute(Slim_Http_Request::METHOD_PUT, $args);
return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_PUT);
}

/**
Expand All @@ -415,7 +424,7 @@ public function put() {
*/
public function delete() {
$args = func_get_args();
return $this->mapRoute(Slim_Http_Request::METHOD_DELETE, $args);
return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_DELETE);
}

/**
Expand Down Expand Up @@ -1006,50 +1015,57 @@ public function clearHooks( $name = null ) {
*/
public function run() {
try {
$this->applyHook('slim.before');
ob_start();
$this->applyHook('slim.before.router');
$dispatched = false;
foreach( $this->router->getMatchedRoutes() as $route ) {
try {
$this->applyHook('slim.before.dispatch');
$dispatched = $route->dispatch();
$this->applyHook('slim.after.dispatch');
if ( $dispatched ) {
break;
try {
$this->applyHook('slim.before');
ob_start();
$this->applyHook('slim.before.router');
$matchedRoutes = $this->router->getMatchedRoutes();
$dispatched = false;
$httpMethod = $this->request()->getMethod();
$httpMethodsAllowed = array();
foreach ( $matchedRoutes as $route ) {
if ( $route->supportsHttpMethod($httpMethod) ) {
try {
$this->applyHook('slim.before.dispatch');
$dispatched = $route->dispatch();
$this->applyHook('slim.after.dispatch');
if ( $dispatched ) {
break;
}
} catch ( Slim_Exception_Pass $e ) {
continue;
}
} else {
$httpMethodsAllowed = array_merge($httpMethodsAllowed, $route->getHttpMethods());
}
} catch ( Slim_Exception_Pass $e ) {
continue;
}
}
if ( !$dispatched ) {
$this->notFound();
}
$this->response()->write(ob_get_clean());
$this->applyHook('slim.after.router');
$this->view->getData('flash')->save();
session_write_close();
$this->response->send();
$this->applyHook('slim.after');
} catch ( Slim_Exception_RequestSlash $e ) {
try {
if ( !$dispatched ) {
if ( $httpMethodsAllowed ) {
$this->response()->header('Allow', implode(' ', $httpMethodsAllowed));
$this->halt(405);
} else {
$this->notFound();
}
}
$this->response()->write(ob_get_clean());
$this->applyHook('slim.after.router');
$this->view->getData('flash')->save();
session_write_close();
$this->response->send();
$this->applyHook('slim.after');
} catch ( Slim_Exception_RequestSlash $e ) {
$this->redirect($this->request->getRootUri() . $this->request->getResourceUri() . '/', 301);
} catch ( Slim_Exception_Stop $e2 ) {
//Ignore Slim_Exception_Stop and exit application context
}
} catch ( Slim_Exception_Stop $e ) {
//Exit application context
} catch ( Exception $e ) {
$this->getLog()->error($e);
try {
} catch ( Exception $e ) {
if ( $e instanceof Slim_Exception_Stop ) throw $e;
$this->getLog()->error($e);
if ( $this->config('debug') === true ) {
$this->halt(500, self::generateErrorMarkup($e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString()));
} else {
$this->error($e);
}
} catch ( Slim_Exception_Stop $e2 ) {
//Ignore Slim_Exception_Stop and exit application context
}
} catch ( Slim_Exception_Stop $e ) {
//Exit application context
}
}

Expand Down
27 changes: 27 additions & 0 deletions tests/RouteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -304,4 +304,31 @@ public function testRouteMiddleware() {

public function callableTestFunction() {}

/**
* Test that a Route manages the HTTP methods that it supports
*
* Case A: Route initially supports no HTTP methods
* Case B: Route can set its supported HTTP methods
* Case C: Route can append supported HTTP methods
* Case D: Route can test if it supports an HTTP method
* Case E: Route can lazily declare supported HTTP methods with `via`
*/
public function testHttpMethods() {
//Case A
$r = new Slim_Route('/foo', function () {});
$this->assertEmpty($r->getHttpMethods());
//Case B
$r->setHttpMethods('GET');
$this->assertEquals(array('GET'), $r->getHttpMethods());
//Case C
$r->appendHttpMethods('POST', 'PUT');
$this->assertEquals(array('GET', 'POST', 'PUT'), $r->getHttpMethods());
//Case D
$this->assertTrue($r->supportsHttpMethod('GET'));
$this->assertFalse($r->supportsHttpMethod('DELETE'));
//Case E
$viaResult = $r->via('DELETE');
$this->assertTrue($viaResult instanceof Slim_Route);
$this->assertTrue($r->supportsHttpMethod('DELETE'));
}
}
37 changes: 26 additions & 11 deletions tests/RouterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public function testGetsAndSetsRequest() {
public function testUrlForNamedRouteWithoutParams() {
$request = new Slim_Http_Request();
$router = new Slim_Router($request);
$route = $router->map('/foo/bar', function () {}, 'GET');
$route = $router->map('/foo/bar', function () {})->via('GET');
$router->cacheNamedRoute('foo', $route);
$this->assertEquals('/foo/bar', $router->urlFor('foo'));
}
Expand All @@ -104,7 +104,7 @@ public function testUrlForNamedRouteWithoutParams() {
public function testUrlForNamedRouteWithParams() {
$request = new Slim_Http_Request();
$router = new Slim_Router($request);
$route = $router->map('/foo/:one/and/:two', function ($one, $two) {}, 'GET');
$route = $router->map('/foo/:one/and/:two', function ($one, $two) {})->via('GET');
$router->cacheNamedRoute('foo', $route);
$this->assertEquals('/foo/Josh/and/John', $router->urlFor('foo', array('one' => 'Josh', 'two' => 'John')));
}
Expand All @@ -117,7 +117,7 @@ public function testUrlForNamedRouteThatDoesNotExist() {
$this->setExpectedException('RuntimeException');
$request = new Slim_Http_Request();
$router = new Slim_Router($request);
$route = $router->map('/foo/bar', function () {}, 'GET');
$route = $router->map('/foo/bar', function () {})->via('GET');
$router->cacheNamedRoute('bar', $route);
$router->urlFor('foo');
}
Expand All @@ -130,8 +130,8 @@ public function testNamedRouteWithExistingName() {
$this->setExpectedException('RuntimeException');
$request = new Slim_Http_Request();
$router = new Slim_Router($request);
$route1 = $router->map('/foo/bar', function () {}, 'GET');
$route2 = $router->map('/foo/bar/2', function () {}, 'GET');
$route1 = $router->map('/foo/bar', function () {})->via('GET');
$route2 = $router->map('/foo/bar/2', function () {})->via('GET');
$router->cacheNamedRoute('bar', $route1);
$router->cacheNamedRoute('bar', $route2);
}
Expand Down Expand Up @@ -186,7 +186,7 @@ public function testErrorHandlerIfNotCallable() {
public function testRouterConsidersHeadAsGet() {
$_SERVER['REQUEST_METHOD'] = 'HEAD';
$router = new Slim_Router(new Slim_Http_Request());
$route = $router->map('/', function () {}, Slim_Http_Request::METHOD_GET);
$route = $router->map('/', function () {})->via('GET', 'HEAD');
$numberOfMatchingRoutes = count($router->getMatchedRoutes());
$this->assertEquals(1, $numberOfMatchingRoutes);
}
Expand All @@ -198,11 +198,11 @@ public function testRouterUrlFor() {
$_SERVER['REQUEST_METHOD'] = 'GET';
$request = new Slim_Http_Request();
$router = new Slim_Router($request);
$route1 = $router->map('/foo/bar', function () {}, 'GET');
$route2 = $router->map('/foo/:one/:two', function () {}, 'GET');
$route3 = $router->map('/foo/:one(/:two)', function () {}, 'GET');
$route4 = $router->map('/foo/:one/(:two/)', function () {}, 'GET');
$route5 = $router->map('/foo/:one/(:two/(:three/))', function () {}, 'GET');
$route1 = $router->map('/foo/bar', function () {})->via('GET');
$route2 = $router->map('/foo/:one/:two', function () {})->via('GET');
$route3 = $router->map('/foo/:one(/:two)', function () {})->via('GET');
$route4 = $router->map('/foo/:one/(:two/)', function () {})->via('GET');
$route5 = $router->map('/foo/:one/(:two/(:three/))', function () {})->via('GET');
$route1->setName('route1');
$route2->setName('route2');
$route3->setName('route3');
Expand Down Expand Up @@ -232,4 +232,19 @@ public function testRouterUrlFor() {
$this->assertEquals('/foo/:one/', $router->urlFor('route5'));
}

/**
* Test that router returns matched routes based on URI only, not
* based on the HTTP method.
*/
public function testRouterMatchesRoutesByUriOnly() {
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['REQUEST_URI'] = '/foo';
$request = new Slim_Http_Request();
$router = new Slim_Router($request);
$router->map('/foo', function () {})->via('GET');
$router->map('/foo', function () {})->via('POST');
$router->map('/foo', function () {})->via('PUT');
$router->map('/foo/bar/xyz', function () {})->via('DELETE');
$this->assertEquals(3, count($router->getMatchedRoutes()));
}
}
Loading

0 comments on commit 716144d

Please sign in to comment.