diff options
author | Arnold Daniels <arnold@jasny.net> | 2016-11-03 23:12:33 +0100 |
---|---|---|
committer | Arnold Daniels <arnold@jasny.net> | 2016-11-03 23:12:33 +0100 |
commit | bd4cb77fbf04923fa31a08fdd1f33f2c0db87864 (patch) | |
tree | 846c5e559360e530d200d897f67b5af02f8795e5 | |
parent | c6bb6b4495804d0f546b5298ad1ac4d66898b351 (diff) | |
download | router-bd4cb77fbf04923fa31a08fdd1f33f2c0db87864.zip router-bd4cb77fbf04923fa31a08fdd1f33f2c0db87864.tar.gz router-bd4cb77fbf04923fa31a08fdd1f33f2c0db87864.tar.bz2 |
Move route binding (used in Glob) to a traitorigin/fix-tests
Added tests for Route
Added missing @covers
-rw-r--r-- | src/Router/Route.php | 26 | ||||
-rw-r--r-- | src/Router/Routes/Glob.php | 198 | ||||
-rw-r--r-- | src/Router/Routes/RouteBinding.php | 314 | ||||
-rw-r--r-- | tests/Router/RouteTest.php | 42 | ||||
-rw-r--r-- | tests/Router/Routes/GlobTest.php | 216 | ||||
-rw-r--r-- | tests/Router/Routes/RouteBindingTest.php | 263 |
6 files changed, 636 insertions, 423 deletions
diff --git a/src/Router/Route.php b/src/Router/Route.php index be5a052..d2158ea 100644 --- a/src/Router/Route.php +++ b/src/Router/Route.php @@ -10,27 +10,21 @@ class Route extends \stdClass /** * Class constructor * - * @param array $values + * @param array|stdClass $values */ - public function __construct(array $values) - { - foreach ($values as $key => $value) { - $this->$key = $value; - } - } - - /** - * Factory method - * - * @param array|\stdClass $values - * @return Route - */ - public static function create($values) + public function __construct($values) { if ($values instanceof \stdClass) { $values = get_object_vars($values); } - return new static($values); + if (!is_array($values)) { + $type = (is_object($values) ? get_class($values) . ' ' : '') . gettype($values); + throw new \InvalidArgumentException("Route values should be an array, not a $type"); + } + + foreach ($values as $key => $value) { + $this->$key = $value; + } } } diff --git a/src/Router/Routes/Glob.php b/src/Router/Routes/Glob.php index 4c6d93c..a4495ee 100644 --- a/src/Router/Routes/Glob.php +++ b/src/Router/Routes/Glob.php @@ -6,6 +6,7 @@ use ArrayObject; use Jasny\Router\UrlParsing; use Jasny\Router\Routes; use Jasny\Router\Route; +use Jasny\Router\Routes\RouteBinding; use Psr\Http\Message\ServerRequestInterface; /** @@ -14,6 +15,7 @@ use Psr\Http\Message\ServerRequestInterface; class Glob extends ArrayObject implements Routes { use UrlParsing; + use RouteBinding; /** * Class constructor @@ -47,7 +49,7 @@ class Glob extends ArrayObject implements Routes throw new \InvalidArgumentException("Unable to create a Route from value " . var_export($value, true)); } - return Route::create($value); + return new Route($value); } /** @@ -162,200 +164,6 @@ class Glob extends ArrayObject implements Routes /** - * Fill out the routes variables based on the url parts. - * - * @param array|\stdClass $vars Route variables - * @param ServerRequestInterface $request - * @param array $parts URL parts - * @return array - */ - protected function bind($vars, ServerRequestInterface $request, array $parts) - { - $values = []; - $type = is_array($vars) && array_keys($vars) === array_keys(array_keys($vars)) ? 'numeric' : 'assoc'; - - foreach ($vars as $key => $var) { - if (!isset($var)) continue; - - if (is_object($var) && !$var instanceof \stdClass) { - $part = array($var); - } elseif (!is_scalar($var)) { - $part = array($this->bind($var, $request, $parts)); - } elseif ($var[0] === '$') { - $options = array_map('trim', explode('|', $var)); - $part = $this->bindVar($type, $request, $parts, $options); - } elseif ($var[0] === '~' && substr($var, -1) === '~') { - $pieces = array_map('trim', explode('~', substr($var, 1, -1))); - $bound = array_filter($this->bind($pieces, $request, $parts)); - $part = array(join('', $bound)); - } else { - $part = array($var); - } - - if ($type === 'assoc') { - $values[$key] = $part[0]; - } else { - $values = array_merge($values, $part); - } - } - - if ($vars instanceof Route) { - $values = Route::create($values); - } elseif (is_object($vars) && $type === 'assoc') { - $values = (object)$values; - } - - return $values; - } - - /** - * Bind variable - * - * @param string $type 'assoc' or 'numeric' - * @param ServerRequestInterface $request - * @param array $parts - * @param array $options - * @return array - */ - protected function bindVar($type, ServerRequestInterface $request, array $parts, array $options) - { - foreach ($options as $option) { - $value = null; - - $bound = - $this->bindVarString($option, $value) || - $this->bindVarSuperGlobal($option, $request, $value) || - $this->bindVarRequestHeader($option, $request, $value) || - $this->bindVarMultipleUrlParts($option, $type, $parts, $value) || - $this->bindVarSingleUrlPart($option, $parts, $value); - - if ($bound && isset($value)) { - return $value; - } - } - - return [null]; - } - - /** - * Bind variable when option is a normal string - * - * @param string $option - * @param mixed $value OUTPUT - * @return boolean - */ - protected function bindVarString($option, &$value) - { - if ($option[0] !== '$') { - $value = [$option]; - return true; - } - - return false; - } - - /** - * Bind variable when option is a super global - * - * @param string $option - * @param mixed $value OUTPUT - * @return boolean - */ - protected function bindVarSuperGlobal($option, ServerRequestInterface $request, &$value) - { - if (preg_match('/^\$_(GET|POST|COOKIE)\[([^\[]*)\]$/i', $option, $matches)) { - list(, $var, $key) = $matches; - - $var = strtolower($var); - $data = null; - - if ($var === 'get') { - $data = $request->getQueryParams(); - } elseif ($var === 'post') { - $data = $request->getParsedBody(); - } elseif ($var === 'cookie') { - $data = $request->getCookieParams(); - } - - $value = isset($data[$key]) ? [$data[$key]] : null; - return true; - } - - return false; - } - - /** - * Bind variable when option is a request header - * - * @param string $option - * @param ServerRequestInterface $request - * @param mixed $value OUTPUT - * @return boolean - */ - protected function bindVarRequestHeader($option, ServerRequestInterface $request, &$value) - { - if (preg_match('/^\$(?:HTTP_)?([A-Z_]+)$/', $option, $matches)) { - $sentence = preg_replace('/[\W_]+/', ' ', $matches[1]); - $name = str_replace(' ', '-', ucwords($sentence)); - - $value = [$request->getHeaderLine($name)]; - return true; - } - - return false; - } - - /** - * Bind variable when option contains multiple URL parts - * - * @param string $option - * @param string $type 'assoc' or 'numeric' - * @param array $parts Url parts - * @param mixed $value OUTPUT - * @return boolean - */ - protected function bindVarMultipleUrlParts($option, $type, array $parts, &$value) - { - if (substr($option, -3) === '...' && ctype_digit(substr($option, 1, -3))) { - $i = (int)substr($option, 1, -3); - - if ($type === 'assoc') { - throw new \InvalidArgumentException("Binding multiple parts using '$option' is only allowed in numeric arrays"); - } else { - $value = array_slice($parts, $i - 1); - } - - return true; - } - - return false; - } - - /** - * Bind variable when option contains a single URL part - * - * @param string $option - * @param array $parts Url parts - * @param mixed $value OUTPUT - * @return boolean - */ - protected function bindVarSingleUrlPart($option, array $parts, &$value) - { - if (ctype_digit(substr($option, 1))) { - $i = (int)substr($option, 1); - $part = array_slice($parts, $i - 1, 1); - - if (!empty($part)) { - $value = $part; - return true; - } - } - - return false; - } - - - /** * Check if a route for the URL exists * * @param ServerRequestInterface $request diff --git a/src/Router/Routes/RouteBinding.php b/src/Router/Routes/RouteBinding.php new file mode 100644 index 0000000..91563c3 --- /dev/null +++ b/src/Router/Routes/RouteBinding.php @@ -0,0 +1,314 @@ +<?php + +namespace Jasny\Router\Routes; + +use Jasny\Router\Route; +use Psr\Http\Message\ServerRequestInterface; + +/** + * Functionality to set route properties based on url parameters + */ +trait RouteBinding +{ + /** + * Fill out the routes variables based on the url parts. + * + * @param array|\stdClass $vars Route variables + * @param ServerRequestInterface $request + * @param array $parts URL parts + * @return array + */ + protected function bind($vars, ServerRequestInterface $request, array $parts) + { + $type = is_array($vars) && array_keys($vars) === array_keys(array_keys($vars)) ? 'numeric' : 'assoc'; + + $values = $this->bindParts($vars, $type, $request, $parts); + + if ($vars instanceof Route) { + $class = get_class($vars); + $values = new $class($values); + } elseif (is_object($vars) && $type === 'assoc') { + $values = (object)$values; + } + + return $values; + } + + + /** + * Fill out the values based on the url parts. + * + * @param array|\stdClass $vars Route variables + * @param string $type + * @param ServerRequestInterface $request + * @param array $parts URL parts + * @return array + */ + protected function bindParts($vars, $type, ServerRequestInterface $request, array $parts) + { + $values = []; + + foreach ($vars as $key => $var) { + $part = null; + + $bound = + $this->bindPartObject($var, $part) || + $this->bindPartArray($var, $request, $parts, $part) || + $this->bindPartVar($var, $type, $request, $parts, $part) || + $this->bindPartConcat($var, $request, $parts, $part) || + $this->bindPartValue($var, $part); + + if (!$bound) continue; + + if ($type === 'assoc') { + $values[$key] = $part[0]; + } else { + $values = array_merge($values, $part); + } + } + + return $values; + } + + /** + * Bind part if it's an object + * + * @param mixed $var + * @param array $part OUTPUT + * @return boolean + */ + protected function bindPartObject($var, &$part) + { + if (!is_object($var) || $var instanceof \stdClass) { + return false; + } + + $part = [$var]; + return true; + } + + /** + * Bind part if it's an array + * + * @param mixed $var + * @param ServerRequestInterface $request + * @param array $parts + * @param array $part OUTPUT + * @return boolean + */ + protected function bindPartArray($var, ServerRequestInterface $request, array $parts, &$part) + { + if (!is_array($var) && !$var instanceof \stdClass) { + return false; + } + + $part = [$this->bind($var, $request, $parts)]; + return true; + } + + /** + * Bind part if it's an variable + * + * @param mixed $var + * @param string $type + * @param ServerRequestInterface $request + * @param array $parts + * @param array $part OUTPUT + * @return boolean + */ + protected function bindPartVar($var, $type, ServerRequestInterface $request, array $parts, &$part) + { + if (!is_string($var) || $var[0] !== '$') { + return false; + } + + $options = array_map('trim', explode('|', $var)); + $part = $this->bindVar($type, $request, $parts, $options); + return true; + } + + /** + * Bind part if it's an concatenation + * + * @param mixed $var + * @param ServerRequestInterface $request + * @param array $parts + * @param array $part OUTPUT + * @return boolean + */ + protected function bindPartConcat($var, ServerRequestInterface $request, array $parts, &$part) + { + if (!is_string($var) || $var[0] !== '~' || substr($var, -1) !== '~') { + return false; + } + + $pieces = array_map('trim', explode('~', substr($var, 1, -1))); + $bound = array_filter($this->bind($pieces, $request, $parts)); + $part = [join('', $bound)]; + + return true; + } + + /** + * Bind part if it's a normal value + * + * @param mixed $var + * @param array $part OUTPUT + * @return boolean + */ + protected function bindPartValue($var, &$part) + { + if (!isset($var)) { + return false; + } + + $part = [$var]; + return true; + } + + /** + * Bind variable + * + * @param string $type 'assoc' or 'numeric' + * @param ServerRequestInterface $request + * @param array $parts + * @param array $options + * @return array + */ + protected function bindVar($type, ServerRequestInterface $request, array $parts, array $options) + { + foreach ($options as $option) { + $value = null; + + $bound = + $this->bindVarString($option, $value) || + $this->bindVarSuperGlobal($option, $request, $value) || + $this->bindVarRequestHeader($option, $request, $value) || + $this->bindVarMultipleUrlParts($option, $type, $parts, $value) || + $this->bindVarSingleUrlPart($option, $parts, $value); + + if ($bound && isset($value)) { + return $value; + } + } + + return [null]; + } + + /** + * Bind variable when option is a normal string + * + * @param string $option + * @param mixed $value OUTPUT + * @return boolean + */ + protected function bindVarString($option, &$value) + { + if ($option[0] !== '$') { + $value = [$option]; + return true; + } + + return false; + } + + /** + * Bind variable when option is a super global + * + * @param string $option + * @param mixed $value OUTPUT + * @return boolean + */ + protected function bindVarSuperGlobal($option, ServerRequestInterface $request, &$value) + { + if (preg_match('/^\$_(GET|POST|COOKIE)\[([^\[]*)\]$/i', $option, $matches)) { + list(, $var, $key) = $matches; + + $var = strtolower($var); + $data = null; + + if ($var === 'get') { + $data = $request->getQueryParams(); + } elseif ($var === 'post') { + $data = $request->getParsedBody(); + } elseif ($var === 'cookie') { + $data = $request->getCookieParams(); + } + + $value = isset($data[$key]) ? [$data[$key]] : null; + return true; + } + + return false; + } + + /** + * Bind variable when option is a request header + * + * @param string $option + * @param ServerRequestInterface $request + * @param mixed $value OUTPUT + * @return boolean + */ + protected function bindVarRequestHeader($option, ServerRequestInterface $request, &$value) + { + if (preg_match('/^\$(?:HTTP_)?([A-Z_]+)$/', $option, $matches)) { + $sentence = preg_replace('/[\W_]+/', ' ', $matches[1]); + $name = str_replace(' ', '-', ucwords($sentence)); + + $value = [$request->getHeaderLine($name)]; + return true; + } + + return false; + } + + /** + * Bind variable when option contains multiple URL parts + * + * @param string $option + * @param string $type 'assoc' or 'numeric' + * @param array $parts Url parts + * @param mixed $value OUTPUT + * @return boolean + */ + protected function bindVarMultipleUrlParts($option, $type, array $parts, &$value) + { + if (substr($option, -3) === '...' && ctype_digit(substr($option, 1, -3))) { + $i = (int)substr($option, 1, -3); + + if ($type === 'assoc') { + throw new \InvalidArgumentException("Binding multiple parts using '$option' is only allowed in numeric arrays"); + } else { + $value = array_slice($parts, $i - 1); + } + + return true; + } + + return false; + } + + /** + * Bind variable when option contains a single URL part + * + * @param string $option + * @param array $parts Url parts + * @param mixed $value OUTPUT + * @return boolean + */ + protected function bindVarSingleUrlPart($option, array $parts, &$value) + { + if (ctype_digit(substr($option, 1))) { + $i = (int)substr($option, 1); + $part = array_slice($parts, $i - 1, 1); + + if (!empty($part)) { + $value = $part; + return true; + } + } + + return false; + } +} diff --git a/tests/Router/RouteTest.php b/tests/Router/RouteTest.php new file mode 100644 index 0000000..4a72d27 --- /dev/null +++ b/tests/Router/RouteTest.php @@ -0,0 +1,42 @@ +<?php + +namespace Jasny\Router; + +use Jasny\Router\Route; + +/** + * @covers Jasny\Router\Route + */ +class RouteTest extends \PHPUnit_Framework_TestCase +{ + public function provider() + { + return [ + [['foo' => '$1', 'color' => 'red', 'number' => 42]], + [(object)['foo' => '$1', 'color' => 'red', 'number' => 42]] + ]; + } + + /** + * @dataProvider provider + * + * @param array|stdClass $values + */ + public function testConstructionWithObject($values) + { + $route = new Route($values); + + $this->assertAttributeSame('$1', 'foo', $route); + $this->assertAttributeSame('red', 'color', $route); + $this->assertAttributeSame(42, 'number', $route); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Route values should be an array, not a string + */ + public function testConstructionInvalidArgument() + { + new Route('foo'); + } +} diff --git a/tests/Router/Routes/GlobTest.php b/tests/Router/Routes/GlobTest.php index a6767a8..5efa77f 100644 --- a/tests/Router/Routes/GlobTest.php +++ b/tests/Router/Routes/GlobTest.php @@ -10,6 +10,10 @@ use ArrayObject; use BadMethodCallException; use InvalidArgumentException; +/** + * @covers Jasny\Router\Routes\Glob + * @covers Jasny\Router\UrlParsing + */ class GlobTest extends \PHPUnit_Framework_TestCase { /** @@ -326,218 +330,6 @@ class GlobTest extends \PHPUnit_Framework_TestCase } /** - * Test binding simple string when getting route - */ - public function testBindVarString() - { - $uri = '/foo/bar'; - $values = [$uri => ['controller' => 'value1', 'check' => 'value1']]; - - $glob = new Glob($values); - $request = $this->getServerRequest($uri); - $route = $glob->getRoute($request); - - $this->assertEquals($values[$uri]['check'], $route->check); - } - - /** - * Provide uri's and corresponding patterns for testBindVarSingleUrlPart() - */ - public function bindVarSingleUrlPartProvider() - { - return [ - ['/*', '/test', ['check' => '$1'], 'test'], - ['/', '/', ['check' => '$1|test'], 'test'], - ['/foo/*/bar', '/foo/test/bar', ['check' => '$2'], 'test'], - ['/foo/bar/*', '/foo/bar/test', ['check' => '$3'], 'test'], - ['/foo/bar/*/zet/*', '/foo/bar/test1/zet/test2', ['check' => '$3', 'checkB' => '$5'], 'test1', 'test2'], - ['/foo/bar/*/zet/*', '/foo/bar/test1/zet/test2', ['check' => '~$3~/~$5~'], 'test1/test2'], - ['/', '/', ['check' => '$foo'], null] - ]; - } - - /** - * Test binding single url part to route option - * @dataProvider bindVarSingleUrlPartProvider - * - * @param string $pattern - * @param string $uri - * @param array $options Route options - * @param string $check Expected value for `check` - * @param string $checkB Expected value for `checkB` - */ - public function testBindVarSingleUrlPart($pattern, $uri, $options, $check, $checkB = null) - { - $values = [$pattern => $options]; - - $glob = new Glob($values); - $request = $this->getServerRequest($uri); - $route = $glob->getRoute($request); - - $this->assertNotNull($route, "Route not found"); - $this->assertInstanceOf(Route::class, $route); - - $this->assertEquals($check, $route->check); - - if (isset($checkB)) { - $this->assertEquals($checkB, $route->checkB); - } - } - - public function testBindVarWithObject() - { - $object = new \Exception(); // Could be anything, just not stdClass - $glob = new Glob(['/' => ['object' => $object]]); - - $request = $this->getServerRequest('/'); - $route = $glob->getRoute($request); - - $this->assertNotNull($route, "Route not found"); - $this->assertInstanceOf(Route::class, $route); - - $this->assertSame($object, $route->object); - } - - public function bindVarWithSubProvider() - { - return [ - [['group' => ['check' => '$1']], 'array'], - [['group' => (object)['check' => '$1']], 'object'], - [['group' => ['sub' => (object)['check' => '$1']]], 'array', 'object'], - [['group' => (object)['sub' => ['check' => '$1']]], 'object', 'array'] - ]; - } - - /** - * @dataProvider bindVarWithSubProvider - * - * @param array $options - * @param string $type - * @param string $subtype - */ - public function testBindVarWithSub(array $options, $type, $subtype = null) - { - $glob = new Glob(['/*' => $options]); - - $request = $this->getServerRequest('/test'); - $route = $glob->getRoute($request); - - $this->assertNotNull($route, "Route not found"); - $this->assertInstanceOf(Route::class, $route); - - $this->assertInternalType($type, $route->group); - - $group = (array)$route->group; - - if (isset($subtype)) { - $this->assertArrayHasKey('sub', $group); - $this->assertInternalType($subtype, $group['sub']); - - $group = (array)$group['sub']; - } - - $this->assertEquals($group, ['check' => 'test']); - } - - - /** - * Provide uri's and corresponding patterns for testBindVarMultipleUrlParts() - */ - public function bindVarMultipleUrlPartsProvider() - { - return [ - ['/foo', ['check' => '$1...'], false, InvalidArgumentException::class], - ['/', ['check' => ['$1...']], false], - ['/foo', ['check' => ['$1...']], true], - ['/foo/bar', ['check' => ['$1...'], 'checkB' => ['$2...']], - InvalidArgumentException::class] - ]; - } - - /** - * Test binding multyple url parts to route option - * @dataProvider bindVarMultipleUrlPartsProvider - * - * @param string $uri - * @param array $options Route options - * @param boolean $positive - * @param string $exception - */ - public function testBindVarMultipleUrlParts($uri, $options, $positive, $exception = false) - { - if ($exception) { - $this->expectException($exception); - } - - $glob = new Glob([$uri => $options]); - $request = $this->getServerRequest($uri); - $route = $glob->getRoute($request); - - if ($exception) return; - - $this->assertNotNull($route, "Route not found"); - $this->assertInstanceOf(Route::class, $route); - - $values = explode('/', trim($uri, '/')); - - $positive ? - $this->assertArraysEqual($values, $route->check, "Multyple url parts are not picked correctly") : - $this->assertEmpty($route->check, "Multyple parts element should be empty"); - - if (!empty($options->checkB)) { - array_shift($values); - $this->assertArraysEqual($values, $route->checkB, "Secondary multyple url parts are not picked correctly"); - } - } - - /** - * Provide uri's and corresponding patterns for testBindVarMultipleUrlParts() - */ - public function bindVarSuperGlobalProvider() - { - return [ - ['/foo', ['check' => '$_GET[check]'], 'get'], - ['/foo', ['check' => '$_POST[check]'], 'post'], - ['/foo', ['check' => '$_COOKIE[check]'], 'cookie'] - ]; - } - - /** - * Test binding element of superglobal array to route option - * @dataProvider bindVarSuperGlobalProvider - * - * @param string $uri - * @param array $options - * @param string $type ('get', 'post', 'cookie') - */ - public function testBindVarSuperGlobal($uri, $options, $type) - { - $test = ['check' => 'test']; - $glob = new Glob([$uri => $options]); - $request = $this->getServerRequest($uri, 'GET', [$type => $test]); - $route = $glob->getRoute($request); - - $this->assertEquals($test['check'], $route->check, "Did not obtaine value for superglobal '$type'"); - } - - /** - * Test binding element of superglobal array to route option - */ - public function testBindVarRequestHeader() - { - $uri = '/foo/bar'; - $test = 'test_header_value'; - - $glob = new Glob([$uri => ['check' => '$HTTP_REFERER']]); - $request = $this->getServerRequest($uri, 'GET', [], $test); - - $route = $glob->getRoute($request); - $this->assertNotNull($route, "Route not found"); - - $this->assertEquals($test, $route->check); - } - - /** * Get ServerRequestInterface object * * @param string $uri diff --git a/tests/Router/Routes/RouteBindingTest.php b/tests/Router/Routes/RouteBindingTest.php new file mode 100644 index 0000000..96536ac --- /dev/null +++ b/tests/Router/Routes/RouteBindingTest.php @@ -0,0 +1,263 @@ +<?php + +namespace Jasny\Router\Routes; + +use Jasny\Router\Routes\Glob; +use Jasny\Router\Route; +use Psr\Http\Message\ServerRequestInterface; + +use InvalidArgumentException; + +/** + * @covers Jasny\Router\Routes\RouteBinding + */ +class RouteBindingTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test binding simple string when getting route + */ + public function testBindVarString() + { + $uri = '/foo/bar'; + $values = [$uri => ['controller' => 'value1', 'check' => 'value1']]; + + $glob = new Glob($values); + $request = $this->getServerRequest($uri); + $route = $glob->getRoute($request); + + $this->assertEquals($values[$uri]['check'], $route->check); + } + + /** + * Provide uri's and corresponding patterns for testBindVarSingleUrlPart() + */ + public function bindVarSingleUrlPartProvider() + { + return [ + ['/*', '/test', ['check' => '$1'], 'test'], + ['/', '/', ['check' => '$1|test'], 'test'], + ['/foo/*/bar', '/foo/test/bar', ['check' => '$2'], 'test'], + ['/foo/bar/*', '/foo/bar/test', ['check' => '$3'], 'test'], + ['/foo/bar/*/zet/*', '/foo/bar/test1/zet/test2', ['check' => '$3', 'checkB' => '$5'], 'test1', 'test2'], + ['/foo/bar/*/zet/*', '/foo/bar/test1/zet/test2', ['check' => '~$3~/~$5~'], 'test1/test2'], + ['/', '/', ['check' => '$foo'], null], + ['/', '/', ['check' => 'test', 'checkB' => null], 'test', null] + ]; + } + + /** + * Test binding single url part to route option + * @dataProvider bindVarSingleUrlPartProvider + * + * @param string $pattern + * @param string $uri + * @param array $options Route options + * @param string $check Expected value for `check` + * @param string $checkB Expected value for `checkB` + */ + public function testBindVarSingleUrlPart($pattern, $uri, $options, $check, $checkB = null) + { + $values = [$pattern => $options]; + + $glob = new Glob($values); + $request = $this->getServerRequest($uri); + $route = $glob->getRoute($request); + + $this->assertNotNull($route, "Route not found"); + $this->assertInstanceOf(Route::class, $route); + + $this->assertEquals($check, $route->check); + + if (isset($checkB)) { + $this->assertEquals($checkB, $route->checkB); + } else { + $this->assertObjectNotHasAttribute('checkB', $route); + } + } + + public function testBindVarWithObject() + { + $object = new \Exception(); // Could be anything, just not stdClass + $glob = new Glob(['/' => ['object' => $object]]); + + $request = $this->getServerRequest('/'); + $route = $glob->getRoute($request); + + $this->assertNotNull($route, "Route not found"); + $this->assertInstanceOf(Route::class, $route); + + $this->assertSame($object, $route->object); + } + + public function bindVarWithSubProvider() + { + return [ + [['group' => ['check' => '$1']], 'array'], + [['group' => (object)['check' => '$1']], 'object'], + [['group' => ['sub' => (object)['check' => '$1']]], 'array', 'object'], + [['group' => (object)['sub' => ['check' => '$1']]], 'object', 'array'] + ]; + } + + /** + * @dataProvider bindVarWithSubProvider + * + * @param array $options + * @param string $type + * @param string $subtype + */ + public function testBindVarWithSub(array $options, $type, $subtype = null) + { + $glob = new Glob(['/*' => $options]); + + $request = $this->getServerRequest('/test'); + $route = $glob->getRoute($request); + + $this->assertNotNull($route, "Route not found"); + $this->assertInstanceOf(Route::class, $route); + + $this->assertInternalType($type, $route->group); + + $group = (array)$route->group; + + if (isset($subtype)) { + $this->assertArrayHasKey('sub', $group); + $this->assertInternalType($subtype, $group['sub']); + + $group = (array)$group['sub']; + } + + $this->assertEquals($group, ['check' => 'test']); + } + + + /** + * Provide uri's and corresponding patterns for testBindVarMultipleUrlParts() + */ + public function bindVarMultipleUrlPartsProvider() + { + return [ + ['/foo', ['check' => '$1...'], false, InvalidArgumentException::class], + ['/', ['check' => ['$1...']], false], + ['/foo', ['check' => ['$1...']], true], + ['/foo/bar', ['check' => ['$1...'], 'checkB' => ['$2...']], + InvalidArgumentException::class] + ]; + } + + /** + * Test binding multyple url parts to route option + * @dataProvider bindVarMultipleUrlPartsProvider + * + * @param string $uri + * @param array $options Route options + * @param boolean $positive + * @param string $exception + */ + public function testBindVarMultipleUrlParts($uri, $options, $positive, $exception = false) + { + if ($exception) { + $this->expectException($exception); + } + + $glob = new Glob([$uri => $options]); + $request = $this->getServerRequest($uri); + $route = $glob->getRoute($request); + + if ($exception) return; + + $this->assertNotNull($route, "Route not found"); + $this->assertInstanceOf(Route::class, $route); + + $values = explode('/', trim($uri, '/')); + + $positive ? + $this->assertArraysEqual($values, $route->check, "Multyple url parts are not picked correctly") : + $this->assertEmpty($route->check, "Multyple parts element should be empty"); + + if (!empty($options->checkB)) { + array_shift($values); + $this->assertArraysEqual($values, $route->checkB, "Secondary multyple url parts are not picked correctly"); + } + } + + /** + * Provide uri's and corresponding patterns for testBindVarMultipleUrlParts() + */ + public function bindVarSuperGlobalProvider() + { + return [ + ['/foo', ['check' => '$_GET[check]'], 'get'], + ['/foo', ['check' => '$_POST[check]'], 'post'], + ['/foo', ['check' => '$_COOKIE[check]'], 'cookie'] + ]; + } + + /** + * Test binding element of superglobal array to route option + * @dataProvider bindVarSuperGlobalProvider + * + * @param string $uri + * @param array $options + * @param string $type ('get', 'post', 'cookie') + */ + public function testBindVarSuperGlobal($uri, $options, $type) + { + $test = ['check' => 'test']; + $glob = new Glob([$uri => $options]); + $request = $this->getServerRequest($uri, 'GET', [$type => $test]); + $route = $glob->getRoute($request); + + $this->assertEquals($test['check'], $route->check, "Did not obtaine value for superglobal '$type'"); + } + + /** + * Test binding element of superglobal array to route option + */ + public function testBindVarRequestHeader() + { + $uri = '/foo/bar'; + $test = 'test_header_value'; + + $glob = new Glob([$uri => ['check' => '$HTTP_REFERER']]); + $request = $this->getServerRequest($uri, 'GET', [], $test); + + $route = $glob->getRoute($request); + $this->assertNotNull($route, "Route not found"); + + $this->assertEquals($test, $route->check); + } + + /** + * Get ServerRequestInterface object + * + * @param string $uri + * @param string $method Http query method + * @return ServerRequestInterface + */ + public function getServerRequest($uri, $method = 'GET', $globals = [], $header = '') + { + $request = $this->createMock(ServerRequestInterface::class); + $request->method('getUri')->willReturn($uri); + $request->method('getMethod')->willReturn($method); + $request->method('getQueryParams')->willReturn(isset($globals['get']) ? $globals['get'] : []); + $request->method('getParsedBody')->willReturn(isset($globals['post']) ? $globals['post'] : []); + $request->method('getCookieParams')->willReturn(isset($globals['cookie']) ? $globals['cookie'] : []); + $request->method('getHeaderLine')->willReturn($header); + + return $request; + } + + /** + * Assert that two 1-dimensional arrays are equal. + * Use if array elements are scalar values, or objects with __toString() method + * + * @param array $array1 + * @param array $array2 + */ + public function assertArraysEqual(array $array1, array $array2) + { + $this->assertEmpty(array_diff($array2, $array1), 'Missing items'); + $this->assertEmpty(array_diff($array1, $array2), 'Additional items'); + } +} |