diff --git a/README.md b/README.md index 969cf17..e4fdf43 100644 --- a/README.md +++ b/README.md @@ -53,10 +53,10 @@ WellRESTed's primary goal is to facilitate mapping of URIs to classes that will // Build the router. $myRouter = new Router(); $myRouter->addRoutes(array( - new StaticRoute("/", "\\myapi\\Handlers\\RootHandler")), - new StaticRoute("/cats/", "\\myapi\\Handlers\\CatCollectionHandler")), - new TemplateRoute("/cats/{id}/", "\\myapi\\Handlers\\CatItemHandler")) -); + new StaticRoute("/", "\\myapi\\Handlers\\RootHandler"), + new StaticRoute("/cats/", "\\myapi\\Handlers\\CatCollectionHandler"), + new TemplateRoute("/cats/{id}/", "\\myapi\\Handlers\\CatItemHandler") +)); $myRouter->respond(); ``` diff --git a/src/pjdietz/WellRESTed/Handler.php b/src/pjdietz/WellRESTed/Handler.php index a1a81af..408c400 100644 --- a/src/pjdietz/WellRESTed/Handler.php +++ b/src/pjdietz/WellRESTed/Handler.php @@ -10,7 +10,6 @@ namespace pjdietz\WellRESTed; -use pjdietz\WellRESTed\Exceptions\HttpExceptions\HttpException; use pjdietz\WellRESTed\Interfaces\HandlerInterface; use pjdietz\WellRESTed\Interfaces\RequestInterface; use pjdietz\WellRESTed\Interfaces\ResponseInterface; @@ -46,12 +45,7 @@ abstract class Handler implements HandlerInterface $this->request = $request; $this->args = $args; $this->response = new Response(); - try { - $this->buildResponse(); - } catch (HttpException $e) { - $this->response->setStatusCode($e->getCode()); - $this->response->setBody($e->getMessage()); - } + $this->buildResponse(); return $this->response; } diff --git a/src/pjdietz/WellRESTed/RouteBuilder.php b/src/pjdietz/WellRESTed/RouteBuilder.php index 09b2e66..f449811 100644 --- a/src/pjdietz/WellRESTed/RouteBuilder.php +++ b/src/pjdietz/WellRESTed/RouteBuilder.php @@ -83,6 +83,8 @@ class RouteBuilder * ->variablePattern is passed to setDefaultVariablePattern() *

* ->vars is passed to setTemplateVars() + * + * @param object */ public function readConfiguration($data) { diff --git a/src/pjdietz/WellRESTed/Router.php b/src/pjdietz/WellRESTed/Router.php index 34bd164..6cf9f54 100644 --- a/src/pjdietz/WellRESTed/Router.php +++ b/src/pjdietz/WellRESTed/Router.php @@ -10,6 +10,7 @@ namespace pjdietz\WellRESTed; +use pjdietz\WellRESTed\Exceptions\HttpExceptions\HttpException; use pjdietz\WellRESTed\Interfaces\HandlerInterface; use pjdietz\WellRESTed\Interfaces\RequestInterface; use pjdietz\WellRESTed\Interfaces\ResponseInterface; @@ -23,11 +24,14 @@ class Router implements HandlerInterface { /** @var array Array of Route objects */ private $routes; + /** @var array Hash array of status code => qualified HandlerInterface names for error handling. */ + private $errorHandlers; /** Create a new Router. */ public function __construct() { $this->routes = array(); + $this->errorHandlers = array(); } /** @@ -41,9 +45,27 @@ class Router implements HandlerInterface { foreach ($this->routes as $route) { /** @var HandlerInterface $route */ - $responce = $route->getResponse($request, $args); - if ($responce) { - return $responce; + try { + $response = $route->getResponse($request, $args); + } catch (HttpException $e) { + $response = new Response(); + $response->setStatusCode($e->getCode()); + $response->setBody($e->getMessage()); + } + if ($response) { + // Check if the router has an error handler for this status code. + $status = $response->getStatusCode(); + if (array_key_exists($status, $this->errorHandlers)) { + /** @var HandlerInterface $errorHandler */ + $errorHandler = new $this->errorHandlers[$status](); + // Pass the response triggering this along to the error handler. + $errorArgs = array("response" => $response); + if ($args) { + $errorArgs = array_merge($args, $errorArgs); + } + return $errorHandler->getResponse($request, $errorArgs); + } + return $response; } } return null; @@ -73,10 +95,34 @@ class Router implements HandlerInterface } } + /** + * Add a custom error handler. + * + * @param integer $statusCode The error status code. + * @param string $errorHandler Fully qualified name to an autoloadable handler class. + */ + public function setErrorHandler($statusCode, $errorHandler) + { + $this->errorHandlers[$statusCode] = $errorHandler; + } + + /** + * Add custom error handlers. + * + * @param array $errorHandlers Array mapping integer error codes to qualified handler names. + */ + public function setErrorHandlers(array $errorHandlers) + { + foreach ($errorHandlers as $statusCode => $errorHandler) { + $this->setErrorHandler($statusCode, $errorHandler); + } + } + /** * Dispatch the singleton Request through the router and output the response. * * Respond with a 404 Not Found if no route provides a response. + * @param array|null $args */ public function respond($args = null) { @@ -89,7 +135,7 @@ class Router implements HandlerInterface } /** - * Prepare a resonse indicating a 404 Not Found error + * Prepare a response indicating a 404 Not Found error * * @param RequestInterface $request * @return ResponseInterface diff --git a/test/HandlerTest.php b/test/HandlerTest.php index ca0f732..5c55ba6 100644 --- a/test/HandlerTest.php +++ b/test/HandlerTest.php @@ -2,7 +2,6 @@ namespace pjdietz\WellRESTed\Test; -use pjdietz\WellRESTed\Exceptions\HttpExceptions\NotFoundException; use pjdietz\WellRESTed\Handler; class HandlerTest extends \PHPUnit_Framework_TestCase @@ -44,18 +43,6 @@ class HandlerTest extends \PHPUnit_Framework_TestCase ]; } - public function testTranslateHttpExceptionToResponse() - { - $mockRequest = $this->getMock('\pjdietz\WellRESTed\Interfaces\RequestInterface'); - $mockRequest->expects($this->any()) - ->method('getMethod') - ->will($this->returnValue("GET")); - - $handler = new ExceptionHandler(); - $resp = $handler->getResponse($mockRequest); - $this->assertEquals(404, $resp->getStatusCode()); - } - public function testReadAllowedMethods() { $mockRequest = $this->getMock('\pjdietz\WellRESTed\Interfaces\RequestInterface'); @@ -71,14 +58,6 @@ class HandlerTest extends \PHPUnit_Framework_TestCase } -class ExceptionHandler extends Handler -{ - protected function get() - { - throw new NotFoundException(); - } -} - class OptionsHandler extends Handler { protected function getAllowedMethods() diff --git a/test/RouterTest.php b/test/RouterTest.php index 5278136..afd9c0a 100644 --- a/test/RouterTest.php +++ b/test/RouterTest.php @@ -2,8 +2,10 @@ namespace pjdietz\WellRESTed\Test; +use pjdietz\WellRESTed\Exceptions\HttpExceptions\ForbiddenException; use pjdietz\WellRESTed\Interfaces\HandlerInterface; use pjdietz\WellRESTed\Interfaces\RequestInterface; +use pjdietz\WellRESTed\Interfaces\ResponseInterface; use pjdietz\WellRESTed\Response; use pjdietz\WellRESTed\Router; use pjdietz\WellRESTed\Routes\StaticRoute; @@ -46,6 +48,75 @@ class RouterTest extends \PHPUnit_Framework_TestCase $this->assertEquals(200, $resp->getStatusCode()); } + public function testRespondWithDefaultErrorForException() + { + $mockRequest = $this->getMock('\pjdietz\WellRESTed\Interfaces\RequestInterface'); + $mockRequest->expects($this->any()) + ->method('getPath') + ->will($this->returnValue("/")); + + $router = new Router(); + $router->addRoute(new StaticRoute("/", __NAMESPACE__ . '\\ForbiddenExceptionHandler')); + $resp = $router->getResponse($mockRequest); + $this->assertEquals(403, $resp->getStatusCode()); + } + + public function testRespondWithErrorHandlerForException() + { + $mockRequest = $this->getMock('\pjdietz\WellRESTed\Interfaces\RequestInterface'); + $mockRequest->expects($this->any()) + ->method('getPath') + ->will($this->returnValue("/")); + + $router = new Router(); + $router->addRoute(new StaticRoute("/", __NAMESPACE__ . '\\ForbiddenExceptionHandler')); + $router->setErrorHandler(403, __NAMESPACE__ . '\\ForbiddenErrorHandler'); + $resp = $router->getResponse($mockRequest); + $this->assertEquals("YOU SHALL NOT PASS!", $resp->getBody()); + } + + public function testRespondWithErrorHandlerForStatusCode() + { + $mockRequest = $this->getMock('\pjdietz\WellRESTed\Interfaces\RequestInterface'); + $mockRequest->expects($this->any()) + ->method('getPath') + ->will($this->returnValue("/")); + + $router = new Router(); + $router->addRoute(new StaticRoute("/", __NAMESPACE__ . '\\ForbiddenHandler')); + $router->setErrorHandler(403, __NAMESPACE__ . '\\ForbiddenErrorHandler'); + $resp = $router->getResponse($mockRequest); + $this->assertEquals("YOU SHALL NOT PASS!", $resp->getBody()); + } + + public function testRespondWithErrorHandlerUsingOriginalResponse() + { + $mockRequest = $this->getMock('\pjdietz\WellRESTed\Interfaces\RequestInterface'); + $mockRequest->expects($this->any()) + ->method('getPath') + ->will($this->returnValue("/")); + + $router = new Router(); + $router->addRoute(new StaticRoute("/", __NAMESPACE__ . '\\MessageHandler')); + $router->setErrorHandlers([404 => __NAMESPACE__ . '\\MessageErrorHandler']); + $resp = $router->getResponse($mockRequest); + $this->assertEquals("

Not Found

", $resp->getBody()); + } + + public function testRespondWithErrorHandlerUsingInjection() + { + $mockRequest = $this->getMock('\pjdietz\WellRESTed\Interfaces\RequestInterface'); + $mockRequest->expects($this->any()) + ->method('getPath') + ->will($this->returnValue("/")); + + $router = new Router(); + $router->addRoute(new StaticRoute("/", __NAMESPACE__ . '\\ForbiddenHandler')); + $router->setErrorHandlers([403 => __NAMESPACE__ . '\\InjectionErrorHandler']); + $resp = $router->getResponse($mockRequest, ["message" => "Pass through"]); + $this->assertEquals("Pass through", $resp->getBody()); + } + public function testReturnNullWhenNoRouteMatches() { $mockRequest = $this->getMock('\pjdietz\WellRESTed\Interfaces\RequestInterface'); @@ -203,6 +274,70 @@ class NotFoundHandler implements HandlerInterface } } +class ForbiddenHandler implements HandlerInterface +{ + public function getResponse(RequestInterface $request, array $args = null) + { + $response = new Response(403); + $response->setBody("Forbidden"); + return $response; + } +} + +class ForbiddenExceptionHandler implements HandlerInterface +{ + public function getResponse(RequestInterface $request, array $args = null) + { + throw new ForbiddenException(); + } +} + +class ForbiddenErrorHandler implements HandlerInterface +{ + public function getResponse(RequestInterface $request, array $args = null) + { + $response = new Response(403); + $response->setBody("YOU SHALL NOT PASS!"); + return $response; + } +} + +class MessageHandler implements HandlerInterface +{ + public function getResponse(RequestInterface $request, array $args = null) + { + $response = new Response(404); + $response->setBody("Not Found"); + return $response; + } +} + +class MessageErrorHandler implements HandlerInterface +{ + public function getResponse(RequestInterface $request, array $args = null) + { + if (isset($args["response"])) { + /** @var ResponseInterface $response */ + $response = $args["response"]; + $message = "

" . $response->getBody() . "

"; + $response->setBody($message); + return $response; + } + return null; + } +} + +class InjectionErrorHandler implements HandlerInterface +{ + public function getResponse(RequestInterface $request, array $args = null) + { + $response = new Response(403); + $response->setBody($args["message"]); + return $response; + } +} + + class InjectionHandler implements HandlerInterface { public function getResponse(RequestInterface $request, array $args = null)