diff options
author | minstel <minstel@yandex.ru> | 2016-10-13 10:23:54 +0300 |
---|---|---|
committer | minstel <minstel@yandex.ru> | 2016-10-13 10:23:54 +0300 |
commit | d9b8f6a9d645f2380f74b8c155429ae7e4793d4d (patch) | |
tree | ff710672973fc97a100af39d7b9dc47c09ac14a4 | |
parent | f9df2a343453c80ac982cb46af60c7b40ace74fc (diff) | |
download | router-d9b8f6a9d645f2380f74b8c155429ae7e4793d4d.zip router-d9b8f6a9d645f2380f74b8c155429ae7e4793d4d.tar.gz router-d9b8f6a9d645f2380f74b8c155429ae7e4793d4d.tar.bz2 |
'Not found' middleware
-rw-r--r-- | src/Router/Middleware/NotFound.php | 105 | ||||
-rw-r--r-- | src/Router/Routes/Glob.php | 6 | ||||
-rw-r--r-- | tests/Router/Middleware/NotFoundTest.php | 190 | ||||
-rw-r--r-- | tests/Router/Routes/GlobTest.php | 35 |
4 files changed, 333 insertions, 3 deletions
diff --git a/src/Router/Middleware/NotFound.php b/src/Router/Middleware/NotFound.php new file mode 100644 index 0000000..56825a7 --- /dev/null +++ b/src/Router/Middleware/NotFound.php @@ -0,0 +1,105 @@ +<?php + +namespace Jasny\Router\Middleware; + +use Jasny\Router\Routes\Glob; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Set response to 'not found' or 'method not allowed' if route is non exist + */ +class NotFound +{ + /** + * Routes + * @var array + */ + protected $routes = []; + + /** + * Action for 'not found' case + * @var callback|int + **/ + protected $notFound = null; + + /** + * Action for 'method not allowed' case + * @var string + **/ + protected $methodNotAllowed = null; + + /** + * Class constructor + * + * @param array $routes + * @param callback|int $notFound + * @param callback|int $methodNotAllowed + */ + public function __construct(array $routes, $notFound = 404, $methodNotAllowed = null) + { + if (!in_array($notFound, [404, '404'], true) && !is_callable($notFound)) { + throw new \InvalidArgumentException("'Not found' parameter should be '404' or a callback"); + } + + if ($methodNotAllowed && !in_array($methodNotAllowed, [405, '405'], true) && !is_callable($methodNotAllowed)) { + throw new \InvalidArgumentException("'Method not allowed' parameter should be '405' or a callback"); + } + + $this->routes = $routes; + $this->notFound = $notFound; + $this->methodNotAllowed = $methodNotAllowed; + } + + /** + * Run middleware action + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param callback $next + * @return ResponseInterface + */ + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next = null) + { + if ($next && !is_callable($next)) { + throw new \InvalidArgumentException("'next' should be a callback"); + } + + $glob = new Glob($this->routes); + + if ($this->methodNotAllowed) { + $notAllowed = !$glob->hasRoute($request) && $glob->hasRoute($request, false); + + if ($notAllowed) { + return is_numeric($this->methodNotAllowed) ? + $this->simpleResponse($response, $this->methodNotAllowed, 'Method Not Allowed') : + call_user_func($this->methodNotAllowed, $request, $response); + } + } + + if (!$glob->hasRoute($request)) { + return is_numeric($this->notFound) ? + $this->simpleResponse($response, $this->notFound, 'Not Found') : + call_user_func($this->notFound, $request, $response); + } + + return $next ? $next($request, $response) : $response; + } + + /** + * Simple response + * + * @param ResponseInterface $response + * @param int $code + * @param string $message + * @return + */ + protected function simpleResponse(ResponseInterface $response, $code, $message) + { + $body = $response->getBody(); + $body->rewind(); + $body->write($message); + + return $response->withStatus($code, $message)->withBody($body); + } +} diff --git a/src/Router/Routes/Glob.php b/src/Router/Routes/Glob.php index 97483d8..a9c2148 100644 --- a/src/Router/Routes/Glob.php +++ b/src/Router/Routes/Glob.php @@ -158,7 +158,7 @@ class Glob extends ArrayObject implements Routes if ($path !== '/') $path = rtrim($path, '/'); if ($this->fnmatch($path, $url)) { - if ((empty($inc) || in_array($method, $inc)) && !in_array($method, $excl)) { + if (!$method || ((empty($inc) || in_array($method, $inc)) && !in_array($method, $excl))) { $ret = $route; break; } @@ -369,9 +369,9 @@ class Glob extends ArrayObject implements Routes * @param ServerRequestInterface $request * @return boolean */ - public function hasRoute(ServerRequestInterface $request) + public function hasRoute(ServerRequestInterface $request, $withMethod = true) { - $route = $this->findRoute($request->getUri(), $request->getMethod()); + $route = $this->findRoute($request->getUri(), $withMethod ? $request->getMethod() : null); return isset($route); } diff --git a/tests/Router/Middleware/NotFoundTest.php b/tests/Router/Middleware/NotFoundTest.php new file mode 100644 index 0000000..fbeccea --- /dev/null +++ b/tests/Router/Middleware/NotFoundTest.php @@ -0,0 +1,190 @@ +<?php + +use Jasny\Router\Middleware\NotFound; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; + +class NotFoundTest extends PHPUnit_Framework_TestCase +{ + /** + * Test creating object with false parameters + * + * @dataProvider constructProvider + * @param string $notFound + * @param string $notAllowed + */ + public function testConstruct($notFound, $notAllowed) + { + $this->expectException(\InvalidArgumentException::class); + + $middleware = new NotFound([], $notFound, $notAllowed); + } + + /** + * Provide data for testing '__contruct' + */ + public function constructProvider() + { + return [ + [404, 406], + [200, 405], + [null, 405], + [true, true] + ]; + } + + /** + * Test invoke with invalid 'next' param + */ + public function testInvokeInvalidNext() + { + $middleware = new NotFound([], 404, 405); + list($request, $response) = $this->getRequests('/foo', 'POST'); + + $this->expectException(\InvalidArgumentException::class); + + $result = $middleware($request, $response, 'not_callable'); + } + + /** + * Test runner __invoke method + * + * @dataProvider invokeProvider + * @param callback|int $notFound + * @param callback|int $notAllowed + * @param callback $next + */ + public function testInvokeNoNext($notFound, $notAllowed, $next) + { + $routes = $this->getRoutes(); + $middleware = new NotFound($routes, $notFound, $notAllowed); + list($request, $response) = $this->getRequests('/foo', 'POST'); + + if (is_numeric($notAllowed)) { + $this->expectSimpleDeny($response, 405, 'Method Not Allowed'); + } elseif (!$notAllowed && is_numeric($notFound)) { + $this->expectSimpleDeny($response, 404, 'Not Found'); + } + + $result = $middleware($request, $response, $next); + + $this->assertEquals(get_class($response), get_class($result), "Result must be an instance of 'ResponseInterface'"); + $this->assertTrue(!isset($result->nextCalled), "'next' was called"); + + if (is_callable($notAllowed)) { + $this->assertTrue($result->notAllowedCalled, "'Not allowed' callback was not called"); + } elseif (!$notAllowed && is_callable($notFound)) { + $this->assertTrue($result->notFoundCalled, "'Not found' callback was not called"); + } + } + + /** + * Text that 'next' callback is invoked when route is found + * + * @dataProvider invokeProvider + * @param callback|int $notFound + * @param callback|int $notAllowed + * @param callback $next + */ + public function testInvokeNext($notFound, $notAllowed, $next) + { + if (!$next) return $this->skipTest(); + + $routes = $this->getRoutes(); + $middleware = new NotFound($routes, $notFound, $notAllowed); + list($request, $response) = $this->getRequests('/foo', 'GET'); + + $result = $middleware($request, $response, $next); + + $this->assertEquals(get_class($response), get_class($result), "Result must be an instance of 'ResponseInterface'"); + $this->assertTrue($result->nextCalled, "'next' waas not called"); + } + + /** + * Provide data for testing invoke method + */ + public function invokeProvider() + { + $callbacks = []; + foreach (['notFound', 'notAllowed', 'next'] as $type) { + $var = $type . 'Called'; + + $callbacks[$type] = function($request, $response) use ($var) { + $response->$var = true; + return $response; + }; + } + + return [ + [404, 405, $callbacks['next']], + [404, 405, null], + [404, null, $callbacks['next']], + [404, null, null], + [$callbacks['notFound'], $callbacks['notAllowed'], $callbacks['next']], + [$callbacks['notFound'], $callbacks['notAllowed'], null], + [$callbacks['notFound'], null, $callbacks['next']], + [$callbacks['notFound'], null, null] + ]; + } + + /** + * Expect that response is set to simple deny answer + * + * @param ResponseInterface $response + * @param int $code + * @param string $reasonPhrase + */ + public function expectSimpleDeny(ResponseInterface $response, $code, $reasonPhrase) + { + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->once())->method('rewind'); + $stream->expects($this->once())->method('write')->with($this->equalTo($reasonPhrase)); + + $response->method('getBody')->will($this->returnValue($stream)); + $response->expects($this->once())->method('withBody')->with($this->equalTo($stream))->will($this->returnSelf()); + $response->expects($this->once())->method('withStatus')->with($this->equalTo($code), $this->equalTo($reasonPhrase))->will($this->returnSelf()); + } + + /** + * Get requests for testing + * + * @param string $uri + * @param string $method + * @return array + */ + public function getRequests($uri, $method) + { + $request = $this->createMock(ServerRequestInterface::class); + $response = $this->createMock(ResponseInterface::class); + + $request->method('getUri')->will($this->returnValue($uri)); + $request->method('getMethod')->will($this->returnValue($method)); + + return [$request, $response]; + } + + /** + * Get routes array + * + * @return array + */ + public function getRoutes() + { + return [ + '/' => ['controller' => 'test'], + '/foo/bar' => ['controller' => 'test'], + '/foo +GET' => ['controller' => 'test'], + '/foo +OPTIONS' => ['controller' => 'test'], + '/bar/foo/zet -POST' => ['controller' => 'test'] + ]; + } + + /** + * Skip the test pass + */ + public function skipTest() + { + return $this->assertTrue(true); + } +} diff --git a/tests/Router/Routes/GlobTest.php b/tests/Router/Routes/GlobTest.php index 565c51b..30ceddc 100644 --- a/tests/Router/Routes/GlobTest.php +++ b/tests/Router/Routes/GlobTest.php @@ -247,6 +247,41 @@ class GlobTest extends PHPUnit_Framework_TestCase } /** + * Test checking if route exists regardless of request method + * + * @dataProvider getHasRouteNoMethodProvider + */ + public function testHasRouteNoMethod($uri, $method) + { + $values = [ + '/' => ['controller' => 'value0'], + '/foo/bar' => ['controller' => 'value1'], + '/foo +GET' => ['controller' => 'value2'], + '/bar/foo/zet -POST' => ['controller' => 'value3'] + ]; + + $glob = new Glob($values); + $request = $this->getServerRequest($uri, $method); + + $this->assertTrue($glob->hasRoute($request, false), "Route not exists"); + } + + /** + * Provide data for creating ServerRequestInterface objects + */ + public function getHasRouteNoMethodProvider() + { + return [ + ['/', 'GET'], + ['/foo/bar', 'GET'], + ['/foo', 'GET'], + ['/foo', 'POST'], + ['/bar/foo/zet', 'GET'], + ['/bar/foo/zet', 'POST'] + ]; + } + + /** * Test binding simple string when getting route */ public function testBindVarString() |