diff --git a/src/Routing/MethodMap.php b/src/Routing/MethodMap.php index d2d4d9b..5e405cd 100644 --- a/src/Routing/MethodMap.php +++ b/src/Routing/MethodMap.php @@ -9,34 +9,40 @@ class MethodMap implements MiddlewareInterface, MethodMapInterface { protected $map; - /** - * @param array $map - */ - public function __construct($map = null) + // ------------------------------------------------------------------------ + + public function __construct() { $this->map = []; - if ($map) { - $this->addMap($map); - } } - /** - * @param array $map - */ - public function addMap($map) - { - foreach ($map as $method => $middleware) { - $this->add($method, $middleware); - } - } + // ------------------------------------------------------------------------ + // MethodMapInterface /** + * Register middleware with a method. + * + * $method may be: + * - A single verb ("GET"), + * - A comma-separated list of verbs ("GET,PUT,DELETE") + * - "*" to indicate any method. + * + * $middleware may be: + * - An instance implementing MiddlewareInterface + * - A string containing the fully qualified class name of a class + * implementing MiddlewareInterface + * - A callable that returns an instance implementing MiddleInterface + * - A callable maching the signature of MiddlewareInteraface::dispatch + * @see DispatchedInterface::dispatch + * + * $middleware may also be null, in which case any previously set + * middleware for that method or methods will be unset. + * * @param string $method * @param mixed $middleware */ - public function add($method, $middleware) + public function setMethod($method, $middleware) { - $method = strtoupper($method); $methods = explode(",", $method); $methods = array_map("trim", $methods); foreach ($methods as $method) { @@ -44,23 +50,32 @@ class MethodMap implements MiddlewareInterface, MethodMapInterface } } + // ------------------------------------------------------------------------ + // MiddlewareInterface + public function dispatch(ServerRequestInterface $request, ResponseInterface &$response) { - $method = strtoupper($request->getMethod()); + $method = $request->getMethod(); // Dispatch middleware registered with the explicitly matching method. if (isset($this->map[$method])) { $middleware = $this->map[$method]; - $this->disptchMiddleware($middleware, $request, $response); + $this->dispatchMiddleware($middleware, $request, $response); return; } // For HEAD, dispatch GET by default. if ($method === "HEAD" && isset($this->map["GET"])) { $middleware = $this->map["GET"]; - $this->disptchMiddleware($middleware, $request, $response); + $this->dispatchMiddleware($middleware, $request, $response); return; } - // Method is not defined. Respond describing the allowed methods, - // either as a 405 response or in response to an OPTIONS request. + // Dispatch * middleware, if registered. + if (isset($this->map["*"])) { + $middleware = $this->map["*"]; + $this->dispatchMiddleware($middleware, $request, $response); + return; + } + // Respond describing the allowed methods, either as a 405 response or + // in response to an OPTIONS request. if ($method === "OPTIONS") { $response = $response->withStatus(200); } else { @@ -69,6 +84,8 @@ class MethodMap implements MiddlewareInterface, MethodMapInterface $this->addAllowHeader($response); } + // ------------------------------------------------------------------------ + protected function addAllowHeader(ResponseInterface &$response) { $methods = join(",", $this->getAllowedMethods()); @@ -100,7 +117,7 @@ class MethodMap implements MiddlewareInterface, MethodMapInterface return new Dispatcher(); } - private function disptchMiddleware($middleware, ServerRequestInterface $request, ResponseInterface &$response) + private function dispatchMiddleware($middleware, ServerRequestInterface $request, ResponseInterface &$response) { $dispatcher = $this->getDispatcher(); $dispatcher->dispatch($middleware, $request, $response); diff --git a/src/Routing/MethodMapInterface.php b/src/Routing/MethodMapInterface.php index 8513080..987be82 100644 --- a/src/Routing/MethodMapInterface.php +++ b/src/Routing/MethodMapInterface.php @@ -2,16 +2,29 @@ namespace WellRESTed\Routing; -interface MethodMapInterface +interface MethodMapInterface extends MiddlewareInterface { /** - * @param array $map - */ - public function addMap($map); - - /** + * Register middleware with a method. + * + * $method may be: + * - A single verb ("GET"), + * - A comma-separated list of verbs ("GET,PUT,DELETE") + * - "*" to indicate any method. + * + * $middleware may be: + * - An instance implementing MiddlewareInterface + * - A string containing the fully qualified class name of a class + * implementing MiddlewareInterface + * - A callable that returns an instance implementing MiddleInterface + * - A callable maching the signature of MiddlewareInteraface::dispatch + * @see DispatchedInterface::dispatch + * + * $middleware may also be null, in which case any previously set + * middleware for that method or methods will be unset. + * * @param string $method * @param mixed $middleware */ - public function add($method, $middleware); + public function setMethod($method, $middleware); } diff --git a/src/Routing/RouteMap.php b/src/Routing/RouteMap.php index 61fb9d4..f06a345 100644 --- a/src/Routing/RouteMap.php +++ b/src/Routing/RouteMap.php @@ -14,7 +14,7 @@ class RouteMap implements RouteMapInterface * - A single verb ("GET"), * - A comma-separated list of verbs ("GET,PUT,DELETE") * - "*" to indicate any method. - * @see MethodMapInterface::addMethod + * @see MethodMapInterface::setMethod * * $target may be: * - An exact path (e.g., "/path/") diff --git a/src/Routing/RouteMapInterface.php b/src/Routing/RouteMapInterface.php index deb8616..cfbc527 100644 --- a/src/Routing/RouteMapInterface.php +++ b/src/Routing/RouteMapInterface.php @@ -11,7 +11,7 @@ interface RouteMapInterface extends MiddlewareInterface * - A single verb ("GET"), * - A comma-separated list of verbs ("GET,PUT,DELETE") * - "*" to indicate any method. - * @see MethodMapInterface::addMethod + * @see MethodMapInterface::setMethod * * $target may be: * - An exact path (e.g., "/path/") diff --git a/src/Routing/Router.php b/src/Routing/Router.php index 215ea92..52acbb1 100644 --- a/src/Routing/Router.php +++ b/src/Routing/Router.php @@ -68,7 +68,7 @@ class Router implements MiddlewareInterface, RouteMapInterface * - A single verb ("GET"), * - A comma-separated list of verbs ("GET,PUT,DELETE") * - "*" to indicate any method. - * @see MethodMapInterface::addMethod + * @see MethodMapInterface::setMethod * * $target may be: * - An exact path (e.g., "/path/") diff --git a/test/tests/unit/Routing/MethodMapTest.php b/test/tests/unit/Routing/MethodMapTest.php index 293251a..bee007e 100644 --- a/test/tests/unit/Routing/MethodMapTest.php +++ b/test/tests/unit/Routing/MethodMapTest.php @@ -6,7 +6,8 @@ use Prophecy\Argument; use WellRESTed\Routing\MethodMap; /** - * @covers WellRESTed\Routing\MethodMap + * @coversDefaultClass WellRESTed\Routing\MethodMap + * @uses WellRESTed\Routing\MethodMap * @uses WellRESTed\Routing\Dispatcher */ class MethodMapTest extends \PHPUnit_Framework_TestCase @@ -22,14 +23,30 @@ class MethodMapTest extends \PHPUnit_Framework_TestCase $this->response->withHeader(Argument::cetera())->willReturn($this->response->reveal()); } + /** + * @covers ::__construct + */ + public function testCreatesInstance() + { + $methodMap = new MethodMap(); + $this->assertNotNull($methodMap); + } + + /** + * @covers ::dispatch + * @covers ::dispatchMiddleware + * @covers ::getDispatcher + * @covers ::setMethod + */ public function testDispatchesMiddlewareWithMatchingMethod() { $this->request->getMethod()->willReturn("GET"); - $middleware = $this->prophesize("\\WellRESTed\\Routing\\MiddlewareInterface"); + $middleware = $this->prophesize('WellRESTed\Routing\MiddlewareInterface'); $middleware->dispatch(Argument::cetera())->willReturn(); - $map = new MethodMap(["GET" => $middleware->reveal()]); + $map = new MethodMap(); + $map->setMethod("GET", $middleware->reveal()); $request = $this->request->reveal(); $response = $this->response->reveal(); @@ -38,14 +55,65 @@ class MethodMapTest extends \PHPUnit_Framework_TestCase $middleware->dispatch($request, $response)->shouldHaveBeenCalled(); } + /** + * @coversNothing + */ + public function testTreatsMethodNamesCaseSensitively() + { + $this->request->getMethod()->willReturn("get"); + + $middlewareUpper = $this->prophesize('WellRESTed\Routing\MiddlewareInterface'); + $middlewareUpper->dispatch(Argument::cetera())->willReturn(); + + $middlewareLower = $this->prophesize('WellRESTed\Routing\MiddlewareInterface'); + $middlewareLower->dispatch(Argument::cetera())->willReturn(); + + $map = new MethodMap(); + $map->setMethod("GET", $middlewareUpper->reveal()); + $map->setMethod("get", $middlewareLower->reveal()); + + $request = $this->request->reveal(); + $response = $this->response->reveal(); + $map->dispatch($request, $response); + + $middlewareLower->dispatch($request, $response)->shouldHaveBeenCalled(); + } + + /** + * @covers ::dispatch + * @covers ::dispatchMiddleware + * @covers ::getDispatcher + * @covers ::setMethod + */ + public function testDispatchesWildcardMiddlewareWithNonMatchingMethod() + { + $this->request->getMethod()->willReturn("GET"); + + $middleware = $this->prophesize('WellRESTed\Routing\MiddlewareInterface'); + $middleware->dispatch(Argument::cetera())->willReturn(); + + $map = new MethodMap(); + $map->setMethod("*", $middleware->reveal()); + + $request = $this->request->reveal(); + $response = $this->response->reveal(); + $map->dispatch($request, $response); + + $middleware->dispatch($request, $response)->shouldHaveBeenCalled(); + } + + /** + * @covers ::dispatch + */ public function testDispatchesGetMiddlewareForHeadByDefault() { $this->request->getMethod()->willReturn("HEAD"); - $middleware = $this->prophesize("\\WellRESTed\\Routing\\MiddlewareInterface"); + $middleware = $this->prophesize('WellRESTed\Routing\MiddlewareInterface'); $middleware->dispatch(Argument::cetera())->willReturn(); - $map = new MethodMap(["GET" => $middleware->reveal()]); + $map = new MethodMap(); + $map->setMethod("GET", $middleware->reveal()); $request = $this->request->reveal(); $response = $this->response->reveal(); @@ -54,13 +122,16 @@ class MethodMapTest extends \PHPUnit_Framework_TestCase $middleware->dispatch($request, $response)->shouldHaveBeenCalled(); } + /* + * @covers ::setMethod + */ public function testRegistersMiddlewareForMultipleMethods() { - $middleware = $this->prophesize("\\WellRESTed\\Routing\\MiddlewareInterface"); + $middleware = $this->prophesize('WellRESTed\Routing\MiddlewareInterface'); $middleware->dispatch(Argument::cetera())->willReturn(); $map = new MethodMap(); - $map->add("GET,POST", $middleware->reveal()); + $map->setMethod("GET,POST", $middleware->reveal()); $request = $this->request->reveal(); $response = $this->response->reveal(); @@ -74,13 +145,36 @@ class MethodMapTest extends \PHPUnit_Framework_TestCase $middleware->dispatch($request, $response)->shouldHaveBeenCalledTimes(2); } + public function testSettingNullUnregistersMiddleware() + { + $this->request->getMethod()->willReturn("POST"); + + $middleware = $this->prophesize('WellRESTed\Routing\MiddlewareInterface'); + + $map = new MethodMap(); + $map->setMethod("POST", $middleware->reveal()); + $map->setMethod("POST", null); + + $request = $this->request->reveal(); + $response = $this->response->reveal(); + $map->dispatch($request, $response); + + $this->response->withStatus(405)->shouldHaveBeenCalled(); + } + + /** + * @covers ::dispatch + * @covers ::addAllowHeader + * @covers ::getAllowedMethods + */ public function testSetsStatusTo200ForOptions() { $this->request->getMethod()->willReturn("OPTIONS"); - $middleware = $this->prophesize("\\WellRESTed\\Routing\\MiddlewareInterface"); + $middleware = $this->prophesize('WellRESTed\Routing\MiddlewareInterface'); - $map = new MethodMap(["GET" => $middleware->reveal()]); + $map = new MethodMap(); + $map->setMethod("GET", $middleware->reveal()); $request = $this->request->reveal(); $response = $this->response->reveal(); @@ -90,17 +184,20 @@ class MethodMapTest extends \PHPUnit_Framework_TestCase } /** + * @covers ::dispatch + * @covers ::addAllowHeader + * @covers ::getAllowedMethods * @dataProvider allowedMethodProvider */ public function testSetsAllowHeaderForOptions($methodsDeclared, $methodsAllowed) { $this->request->getMethod()->willReturn("OPTIONS"); - $middleware = $this->prophesize("\\WellRESTed\\Routing\\MiddlewareInterface"); + $middleware = $this->prophesize('WellRESTed\Routing\MiddlewareInterface'); $map = new MethodMap(); foreach ($methodsDeclared as $method) { - $map->add($method, $middleware->reveal()); + $map->setMethod($method, $middleware->reveal()); } $request = $this->request->reveal(); @@ -118,13 +215,20 @@ class MethodMapTest extends \PHPUnit_Framework_TestCase $this->response->withHeader("Allow", Argument::that($containsAllMethods))->shouldHaveBeenCalled(); } + /** + * @covers ::dispatch + * @covers ::addAllowHeader + * @covers ::getAllowedMethods + * @dataProvider allowedMethodProvider + */ public function testSetsStatusTo405ForBadMethod() { $this->request->getMethod()->willReturn("POST"); - $middleware = $this->prophesize("\\WellRESTed\\Routing\\MiddlewareInterface"); + $middleware = $this->prophesize('WellRESTed\Routing\MiddlewareInterface'); - $map = new MethodMap(["GET" => $middleware->reveal()]); + $map = new MethodMap(); + $map->setMethod("GET", $middleware->reveal()); $request = $this->request->reveal(); $response = $this->response->reveal(); @@ -134,17 +238,20 @@ class MethodMapTest extends \PHPUnit_Framework_TestCase } /** + * @covers ::dispatch + * @covers ::addAllowHeader + * @covers ::getAllowedMethods * @dataProvider allowedMethodProvider */ - public function testSetsAlloweHeaderForBadMethod($methodsDeclared, $methodsAllowed) + public function testSetsAllowHeaderForBadMethod($methodsDeclared, $methodsAllowed) { $this->request->getMethod()->willReturn("BAD"); - $middleware = $this->prophesize("\\WellRESTed\\Routing\\MiddlewareInterface"); + $middleware = $this->prophesize('WellRESTed\Routing\MiddlewareInterface'); $map = new MethodMap(); foreach ($methodsDeclared as $method) { - $map->add($method, $middleware->reveal()); + $map->setMethod($method, $middleware->reveal()); } $request = $this->request->reveal();