diff options
author | minstel <minstel@yandex.ru> | 2016-10-17 10:04:59 +0300 |
---|---|---|
committer | minstel <minstel@yandex.ru> | 2016-10-17 10:04:59 +0300 |
commit | ea5556fe7059fe07bd6fe85c9a656f637d04309b (patch) | |
tree | f9193b5100ffbc2bed7d694bfe17e5da7f6b9200 | |
parent | f9df2a343453c80ac982cb46af60c7b40ace74fc (diff) | |
download | router-ea5556fe7059fe07bd6fe85c9a656f637d04309b.zip router-ea5556fe7059fe07bd6fe85c9a656f637d04309b.tar.gz router-ea5556fe7059fe07bd6fe85c9a656f637d04309b.tar.bz2 |
'Base Path' middleware
-rw-r--r-- | src/Router/Middleware/BasePath.php | 121 | ||||
-rw-r--r-- | tests/Router/Middleware/BasePathTest.php | 282 |
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()); + } +} |