summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorminstel <minstel@yandex.ru>2016-10-13 10:23:54 +0300
committerminstel <minstel@yandex.ru>2016-10-13 10:23:54 +0300
commitd9b8f6a9d645f2380f74b8c155429ae7e4793d4d (patch)
treeff710672973fc97a100af39d7b9dc47c09ac14a4
parentf9df2a343453c80ac982cb46af60c7b40ace74fc (diff)
downloadrouter-d9b8f6a9d645f2380f74b8c155429ae7e4793d4d.zip
router-d9b8f6a9d645f2380f74b8c155429ae7e4793d4d.tar.gz
router-d9b8f6a9d645f2380f74b8c155429ae7e4793d4d.tar.bz2
'Not found' middleware
-rw-r--r--src/Router/Middleware/NotFound.php105
-rw-r--r--src/Router/Routes/Glob.php6
-rw-r--r--tests/Router/Middleware/NotFoundTest.php190
-rw-r--r--tests/Router/Routes/GlobTest.php35
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()