summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorminstel <minstel@yandex.ru>2016-10-17 10:04:59 +0300
committerminstel <minstel@yandex.ru>2016-10-17 10:04:59 +0300
commitea5556fe7059fe07bd6fe85c9a656f637d04309b (patch)
treef9193b5100ffbc2bed7d694bfe17e5da7f6b9200
parentf9df2a343453c80ac982cb46af60c7b40ace74fc (diff)
downloadrouter-ea5556fe7059fe07bd6fe85c9a656f637d04309b.zip
router-ea5556fe7059fe07bd6fe85c9a656f637d04309b.tar.gz
router-ea5556fe7059fe07bd6fe85c9a656f637d04309b.tar.bz2
'Base Path' middleware
-rw-r--r--src/Router/Middleware/BasePath.php121
-rw-r--r--tests/Router/Middleware/BasePathTest.php282
2 files changed, 403 insertions, 0 deletions
diff --git a/src/Router/Middleware/BasePath.php b/src/Router/Middleware/BasePath.php
new file mode 100644
index 0000000..a209a1f
--- /dev/null
+++ b/src/Router/Middleware/BasePath.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Jasny\Router\Middleware;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Set base path for request
+ */
+class BasePath
+{
+ /**
+ * Base path
+ * @var string
+ **/
+ protected $basePath = '';
+
+ /**
+ * Set base path
+ *
+ * @param string $basePath
+ */
+ public function __construct($basePath)
+ {
+ if (!$basePath || !is_string($basePath) || $basePath === '/') {
+ throw new \InvalidArgumentException("Base path must be a string with at list one url segment");
+ }
+
+ $this->basePath = $this->normalizePath($basePath);
+ }
+
+ /**
+ * Get base path
+ *
+ * @return string
+ */
+ public function getBasePath()
+ {
+ return $this->basePath;
+ }
+
+ /**
+ * 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");
+ }
+
+ $uri = $request->getUri();
+ $path = $this->normalizePath($uri->getPath());
+
+ if (!$this->hasBasePath($path)) return $this->setError($response);
+ if (!$next) return $response;
+
+ $noBase = $this->getBaselessPath($path);
+ $uri = $uri->withPath($noBase);
+ $request = $request->withUri($uri)->withAttribute('original_uri', $path);
+
+ return call_user_func($next, $request, $response);
+ }
+
+ /**
+ * Remove base path from given path
+ *
+ * @param string $path
+ * @return string
+ */
+ protected function getBaselessPath($path)
+ {
+ $path = preg_replace('|^' . preg_quote($this->getBasePath()) . '|i', '', $path);
+
+ return $path ?: '/';
+ }
+
+ /**
+ * Normalize path
+ *
+ * @param string $path
+ * @return string
+ */
+ protected function normalizePath($path)
+ {
+ return '/' . trim($path, '/');
+ }
+
+ /**
+ * Check that path starts with base path
+ *
+ * @param string $path
+ * @return boolean
+ */
+ protected function hasBasePath($path)
+ {
+ return preg_match('#^' . preg_quote($this->getBasePath()) . '(\/|$)#i', $path);
+ }
+
+ /**
+ * Set error response
+ *
+ * @param ResponseInterface $response
+ * @return ResponseInterface
+ */
+ protected function setError($response)
+ {
+ $message = 'Not Found';
+
+ $body = $response->getBody();
+ $body->rewind();
+ $body->write($message);
+
+ return $response->withStatus(404, $message)->withBody($body);
+ }
+}
diff --git a/tests/Router/Middleware/BasePathTest.php b/tests/Router/Middleware/BasePathTest.php
new file mode 100644
index 0000000..4edaba0
--- /dev/null
+++ b/tests/Router/Middleware/BasePathTest.php
@@ -0,0 +1,282 @@
+<?php
+
+use Jasny\Router\Middleware\BasePath;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\UriInterface;
+
+class BasePathTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Test creating middleware with invalid parameter
+ *
+ * @dataProvider invalidConstructProvider
+ */
+ public function testInvalidConstruct($basePath)
+ {
+ $this->expectException(\InvalidArgumentException::class);
+
+ $pathHandler = new BasePath($basePath);
+ }
+
+ /**
+ * Provide data for testing invalid BasePath creation
+ *
+ * @return array
+ */
+ public function invalidConstructProvider()
+ {
+ return [
+ [''],
+ ['/'],
+ [null],
+ [false],
+ [['test']],
+ [(object)['test']],
+ [12345]
+ ];
+ }
+
+ /**
+ * Test creating BasePath instance
+ *
+ * @dataProvider validConstructProvider
+ * @param string $basePath
+ */
+ public function testValidConstruct($basePath)
+ {
+ $pathHandler = new BasePath($basePath);
+
+ $this->assertNotEmpty($pathHandler->getBasePath(), "Empty base path");
+ $this->assertEquals($this->normalizePath($basePath), $pathHandler->getBasePath(), "Base path was not set correctly");
+ }
+
+ /**
+ * Provide data for testing BasePath creation
+ *
+ * @return array
+ */
+ public function validConstructProvider()
+ {
+ return [
+ ['/foo'],
+ ['/foo/'],
+ ['foo/'],
+ ['/foo/bar'],
+ ['foo/bar'],
+ ['/foo/bar/'],
+ ['/foo/bar-zet/']
+ ];
+ }
+
+ /**
+ * Test invoke with invalid 'next' param
+ */
+ public function testInvokeInvalidNext()
+ {
+ $middleware = new BasePath('/foo');
+ list($request, $response) = $this->getRequests();
+
+ $this->expectException(\InvalidArgumentException::class);
+
+ $result = $middleware($request, $response, 'not_callable');
+ }
+
+ /**
+ * Test case when given request path does not starts with given base path
+ *
+ * @dataProvider notFoundProvider
+ * @param string $basePath
+ * @param string $path
+ */
+ public function testNotFound($basePath, $path)
+ {
+ $middleware = new BasePath($basePath);
+ list($request, $response) = $this->getRequests();
+
+ $this->expectRequestGetPath($request, $path);
+ $this->expectNotFound($response);
+
+ $result = $middleware($request, $response, function($response, $request) {
+ $response->nextCalled = true;
+
+ return $response;
+ });
+
+ $this->assertEquals(get_class($response), get_class($result), "Middleware should return response object");
+ $this->assertFalse(isset($response->nextCalled), "'next' was called");
+ }
+
+ /**
+ * Provide data for testing BasePath creation
+ *
+ * @return array
+ */
+ public function notFoundProvider()
+ {
+ return [
+ ['/foo', '/bar'],
+ ['/foo', '/bar/foo'],
+ ['/foo/bar', '/zet/foo/bar'],
+ ['/foo/bar', '/foo/bar-/teta'],
+ ['/foo/bar', '/foo/bar-zet/teta'],
+ ['/foo/bar', '/foo/ba'],
+ ['/foo/bar', '/foo'],
+ ['/f', '/foo'],
+ ];
+ }
+
+ /**
+ * Test correct case, when path contains base path
+ *
+ * @dataProvider foundProvider
+ * @param string $basePath
+ * @param string $path
+ */
+ public function testFound($basePath, $path)
+ {
+ $middleware = new BasePath($basePath);
+ list($request, $response) = $this->getRequests();
+
+ $this->expectRequestSetBasePath($request, $basePath, $path);
+
+ $result = $middleware($request, $response, function($request, $response) {
+ $response->nextCalled = true;
+
+ return $response;
+ });
+
+ $this->assertEquals(get_class($response), get_class($result), "Middleware should return response object");
+ $this->assertTrue($response->nextCalled, "'next' was not called");
+ }
+
+
+ /**
+ * Test correct case, when path contains base path, with no 'next' callback
+ *
+ * @dataProvider foundProvider
+ * @param string $basePath
+ * @param string $path
+ */
+ public function testFoundNoNext($basePath, $path)
+ {
+ $middleware = new BasePath($basePath);
+ list($request, $response) = $this->getRequests();
+
+ $this->expectRequestGetPath($request, $path);
+
+ $result = $middleware($request, $response);
+
+ $this->assertEquals($response, $result, "Middleware should return response object");
+ }
+
+ /**
+ * Provide data for testing BasePath creation
+ *
+ * @return array
+ */
+ public function foundProvider()
+ {
+ return [
+ ['/foo', '/foo'],
+ ['foo', '/foo'],
+ ['/foo', '/foo/bar'],
+ ['/foo/bar', '/foo/bar'],
+ ['foo/bar', '/foo/bar'],
+ ['/foo/bar', '/foo/bar/zet'],
+ ['/f', '/f/foo'],
+ ['f', '/f/foo'],
+ ];
+ }
+
+ /**
+ * Normalize path
+ *
+ * @param string $path
+ * @return string
+ */
+ public function normalizePath($path)
+ {
+ return '/' . trim($path, '/');
+ }
+
+ /**
+ * Remove base path from given path
+ *
+ * @param string $path
+ * @return string
+ */
+ protected function getBaselessPath($basePath, $path)
+ {
+ $basePath = $this->normalizePath($basePath);
+ $path = $this->normalizePath($path);
+
+ $path = preg_replace('|^' . preg_quote($basePath) . '|i', '', $path);
+
+ return $path ?: '/';
+ }
+
+ /**
+ * Get requests for testing
+ *
+ * @param string $path
+ * @return array
+ */
+ public function getRequests($path = null)
+ {
+ $request = $this->createMock(ServerRequestInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
+
+ return [$request, $response];
+ }
+
+ /**
+ * Expect that request will return a path
+ *
+ * @param ServerRequestInterface $request
+ * @param string $path
+ */
+ public function expectRequestGetPath(ServerRequestInterface $request, $path)
+ {
+ $uri = $this->createMock(UriInterface::class);
+ $uri->expects($this->once())->method('getPath')->will($this->returnValue($path));
+ $request->expects($this->once())->method('getUri')->will($this->returnValue($uri));
+ }
+
+ /**
+ * Expect for setting base path for request
+ *
+ * @param ServerRequestInterface $request
+ * @param string $basePath
+ * @param string $path
+ */
+ public function expectRequestSetBasePath(ServerRequestInterface $request, $basePath, $path)
+ {
+ $noBase = $this->getBaselessPath($basePath, $path);
+
+ $uri = $this->createMock(UriInterface::class);
+ $uri->expects($this->once())->method('getPath')->will($this->returnValue($path));
+ $uri->expects($this->once())->method('withPath')->with($this->equalTo($noBase))->will($this->returnSelf());
+
+ $request->expects($this->once())->method('getUri')->will($this->returnValue($uri));
+ $request->expects($this->once())->method('withUri')->with($this->equalTo($uri))->will($this->returnSelf());
+ $request->expects($this->once())->method('withAttribute')->with($this->equalTo('original_uri'), $this->equalTo($path))->will($this->returnSelf());
+ }
+
+ /**
+ * Expect for not found error
+ *
+ * @param ResponseInterface $response
+ */
+ public function expectNotFound(ResponseInterface $response)
+ {
+ $stream = $this->createMock(StreamInterface::class);
+ $stream->expects($this->once())->method('rewind');
+ $stream->expects($this->once())->method('write')->with($this->equalTo('Not Found'));
+
+ $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(404), $this->equalTo('Not Found'))->will($this->returnSelf());
+ }
+}