1
0
mirror of https://github.com/dannyvankooten/AltoRouter.git synced 2025-08-11 02:44:03 +02:00

Merge pull request #234 from DevFelixDorn/code-maintainability

Add PHPCS & type-hinting for array function arguments. Use (CS enforced) short array notation everywhere. Thanks @DevFelixDorn
This commit is contained in:
Danny van Kooten
2019-11-10 20:18:39 +01:00
committed by GitHub
6 changed files with 822 additions and 748 deletions

View File

@@ -11,17 +11,18 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
class AltoRouter { class AltoRouter
{
/** /**
* @var array Array of all routes (incl. named routes). * @var array Array of all routes (incl. named routes).
*/ */
protected $routes = array(); protected $routes = [];
/** /**
* @var array Array of all named routes. * @var array Array of all named routes.
*/ */
protected $namedRoutes = array(); protected $namedRoutes = [];
/** /**
* @var string Can be used to ignore leading part of the Request URL (if main file lives in subdirectory of host) * @var string Can be used to ignore leading part of the Request URL (if main file lives in subdirectory of host)
@@ -31,14 +32,14 @@ class AltoRouter {
/** /**
* @var array Array of default match types (regex helpers) * @var array Array of default match types (regex helpers)
*/ */
protected $matchTypes = array( protected $matchTypes = [
'i' => '[0-9]++', 'i' => '[0-9]++',
'a' => '[0-9A-Za-z]++', 'a' => '[0-9A-Za-z]++',
'h' => '[0-9A-Fa-f]++', 'h' => '[0-9A-Fa-f]++',
'*' => '.+?', '*' => '.+?',
'**' => '.++', '**' => '.++',
'' => '[^/\.]++' '' => '[^/\.]++'
); ];
/** /**
* Create router in one call from config. * Create router in one call from config.
@@ -46,8 +47,10 @@ class AltoRouter {
* @param array $routes * @param array $routes
* @param string $basePath * @param string $basePath
* @param array $matchTypes * @param array $matchTypes
* @throws Exception
*/ */
public function __construct( $routes = array(), $basePath = '', $matchTypes = array() ) { public function __construct(array $routes = [], $basePath = '', $matchTypes = [])
{
$this->addRoutes($routes); $this->addRoutes($routes);
$this->setBasePath($basePath); $this->setBasePath($basePath);
$this->addMatchTypes($matchTypes); $this->addMatchTypes($matchTypes);
@@ -58,36 +61,40 @@ class AltoRouter {
* Useful if you want to process or display routes. * Useful if you want to process or display routes.
* @return array All routes. * @return array All routes.
*/ */
public function getRoutes() { public function getRoutes()
{
return $this->routes; return $this->routes;
} }
/** /**
* Add multiple routes at once from array in the following format: * Add multiple routes at once from array in the following format:
* *
* $routes = array( * $routes = [
* array($method, $route, $target, $name) * [$method, $route, $target, $name]
* ); * ];
* *
* @param array $routes * @param array $routes
* @return void * @return void
* @author Koen Punt * @author Koen Punt
* @throws Exception * @throws Exception
*/ */
public function addRoutes($routes){ public function addRoutes($routes)
if(!is_array($routes) && !$routes instanceof Traversable) { {
throw new \Exception('Routes should be an array or an instance of Traversable'); if (!is_array($routes) && !$routes instanceof Traversable) {
throw new RuntimeException('Routes should be an array or an instance of Traversable');
} }
foreach($routes as $route) { foreach ($routes as $route) {
call_user_func_array(array($this, 'map'), $route); call_user_func_array([$this, 'map'], $route);
} }
} }
/** /**
* Set the base path. * Set the base path.
* Useful if you are running your application from a subdirectory. * Useful if you are running your application from a subdirectory.
* @param string $basePath
*/ */
public function setBasePath($basePath) { public function setBasePath($basePath)
{
$this->basePath = $basePath; $this->basePath = $basePath;
} }
@@ -96,7 +103,8 @@ class AltoRouter {
* *
* @param array $matchTypes The key is the name and the value is the regex. * @param array $matchTypes The key is the name and the value is the regex.
*/ */
public function addMatchTypes($matchTypes) { public function addMatchTypes(array $matchTypes)
{
$this->matchTypes = array_merge($this->matchTypes, $matchTypes); $this->matchTypes = array_merge($this->matchTypes, $matchTypes);
} }
@@ -109,17 +117,16 @@ class AltoRouter {
* @param string $name Optional name of this route. Supply if you want to reverse route this url in your application. * @param string $name Optional name of this route. Supply if you want to reverse route this url in your application.
* @throws Exception * @throws Exception
*/ */
public function map($method, $route, $target, $name = null) { public function map($method, $route, $target, $name = null)
{
$this->routes[] = array($method, $route, $target, $name); $this->routes[] = [$method, $route, $target, $name];
if($name) { if ($name) {
if(isset($this->namedRoutes[$name])) { if (isset($this->namedRoutes[$name])) {
throw new \Exception("Can not redeclare route '{$name}'"); throw new RuntimeException("Can not redeclare route '{$name}'");
} else {
$this->namedRoutes[$name] = $route;
} }
$this->namedRoutes[$name] = $route;
} }
return; return;
@@ -135,11 +142,12 @@ class AltoRouter {
* @return string The URL of the route with named parameters in place. * @return string The URL of the route with named parameters in place.
* @throws Exception * @throws Exception
*/ */
public function generate($routeName, array $params = array()) { public function generate($routeName, array $params = [])
{
// Check if named route exists // Check if named route exists
if(!isset($this->namedRoutes[$routeName])) { if (!isset($this->namedRoutes[$routeName])) {
throw new \Exception("Route '{$routeName}' does not exist."); throw new RuntimeException("Route '{$routeName}' does not exist.");
} }
// Replace named parameters // Replace named parameters
@@ -149,26 +157,24 @@ class AltoRouter {
$url = $this->basePath . $route; $url = $this->basePath . $route;
if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) { if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
foreach ($matches as $index => $match) {
foreach($matches as $index => $match) {
list($block, $pre, $type, $param, $optional) = $match; list($block, $pre, $type, $param, $optional) = $match;
if ($pre) { if ($pre) {
$block = substr($block, 1); $block = substr($block, 1);
} }
if(isset($params[$param])) { if (isset($params[$param])) {
// Part is found, replace for param value // Part is found, replace for param value
$url = str_replace($block, $params[$param], $url); $url = str_replace($block, $params[$param], $url);
} elseif ($optional && $index !== 0) { } elseif ($optional && $index !== 0) {
// Only strip preceeding slash if it's not at the base // Only strip preceding slash if it's not at the base
$url = str_replace($pre . $block, '', $url); $url = str_replace($pre . $block, '', $url);
} else { } else {
// Strip match block // Strip match block
$url = str_replace($block, '', $url); $url = str_replace($block, '', $url);
} }
} }
} }
return $url; return $url;
@@ -180,13 +186,13 @@ class AltoRouter {
* @param string $requestMethod * @param string $requestMethod
* @return array|boolean Array with route information on success, false on failure (no match). * @return array|boolean Array with route information on success, false on failure (no match).
*/ */
public function match($requestUrl = null, $requestMethod = null) { public function match($requestUrl = null, $requestMethod = null)
{
$params = array(); $params = [];
$match = false;
// set Request Url if it isn't passed as parameter // set Request Url if it isn't passed as parameter
if($requestUrl === null) { if ($requestUrl === null) {
$requestUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/'; $requestUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
} }
@@ -199,17 +205,19 @@ class AltoRouter {
} }
// set Request Method if it isn't passed as a parameter // set Request Method if it isn't passed as a parameter
if($requestMethod === null) { if ($requestMethod === null) {
$requestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; $requestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
} }
foreach($this->routes as $handler) { foreach ($this->routes as $handler) {
list($methods, $route, $target, $name) = $handler; list($methods, $route, $target, $name) = $handler;
$method_match = (stripos($methods, $requestMethod) !== false); $method_match = (stripos($methods, $requestMethod) !== false);
// Method did not match, continue to next route. // Method did not match, continue to next route.
if (!$method_match) continue; if (!$method_match) {
continue;
}
if ($route === '*') { if ($route === '*') {
// * wildcard (matches all) // * wildcard (matches all)
@@ -231,18 +239,19 @@ class AltoRouter {
} }
if ($match) { if ($match) {
if ($params) { if ($params) {
foreach($params as $key => $value) { foreach ($params as $key => $value) {
if(is_numeric($key)) unset($params[$key]); if (is_numeric($key)) {
unset($params[$key]);
}
} }
} }
return array( return [
'target' => $target, 'target' => $target,
'params' => $params, 'params' => $params,
'name' => $name 'name' => $name
); ];
} }
} }
return false; return false;
@@ -250,12 +259,14 @@ class AltoRouter {
/** /**
* Compile the regex for a given route (EXPENSIVE) * Compile the regex for a given route (EXPENSIVE)
* @param $route
* @return string
*/ */
protected function compileRoute($route) { protected function compileRoute($route)
{
if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) { if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
$matchTypes = $this->matchTypes; $matchTypes = $this->matchTypes;
foreach($matches as $match) { foreach ($matches as $match) {
list($block, $pre, $type, $param, $optional) = $match; list($block, $pre, $type, $param, $optional) = $match;
if (isset($matchTypes[$type])) { if (isset($matchTypes[$type])) {
@@ -280,7 +291,6 @@ class AltoRouter {
$route = str_replace($block, $pattern, $route); $route = str_replace($block, $pattern, $route);
} }
} }
return "`^$route$`u"; return "`^$route$`u";
} }

View File

@@ -24,7 +24,8 @@
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "5.7.*", "phpunit/phpunit": "5.7.*",
"codeclimate/php-test-reporter": "dev-master" "codeclimate/php-test-reporter": "dev-master",
"squizlabs/php_codesniffer": "3.4.2"
}, },
"autoload": { "autoload": {
"classmap": ["AltoRouter.php"] "classmap": ["AltoRouter.php"]

View File

@@ -12,10 +12,10 @@ if (file_exists($_SERVER['SCRIPT_FILENAME']) && pathinfo($_SERVER['SCRIPT_FILENA
$router = new AltoRouter(); $router = new AltoRouter();
$router->setBasePath('/AltoRouter/examples/basic'); $router->setBasePath('/AltoRouter/examples/basic');
$router->map('GET|POST','/', 'home#index', 'home'); $router->map('GET|POST', '/', 'home#index', 'home');
$router->map('GET','/users/', array('c' => 'UserController', 'a' => 'ListAction')); $router->map('GET', '/users/', ['c' => 'UserController', 'a' => 'ListAction']);
$router->map('GET','/users/[i:id]', 'users#show', 'users_show'); $router->map('GET', '/users/[i:id]', 'users#show', 'users_show');
$router->map('POST','/users/[i:id]/[delete|update:action]', 'usersController#doAction', 'users_do'); $router->map('POST', '/users/[i:id]/[delete|update:action]', 'usersController#doAction', 'users_do');
// match current request // match current request
$match = $router->match(); $match = $router->match();
@@ -31,5 +31,5 @@ $match = $router->match();
<h3>Try these requests: </h3> <h3>Try these requests: </h3>
<p><a href="<?php echo $router->generate('home'); ?>">GET <?php echo $router->generate('home'); ?></a></p> <p><a href="<?php echo $router->generate('home'); ?>">GET <?php echo $router->generate('home'); ?></a></p>
<p><a href="<?php echo $router->generate('users_show', array('id' => 5)); ?>">GET <?php echo $router->generate('users_show', array('id' => 5)); ?></a></p> <p><a href="<?php echo $router->generate('users_show', ['id' => 5]); ?>">GET <?php echo $router->generate('users_show', ['id' => 5]); ?></a></p>
<p><form action="<?php echo $router->generate('users_do', array('id' => 10, 'action' => 'update')); ?>" method="post"><button type="submit"><?php echo $router->generate('users_do', array('id' => 10, 'action' => 'update')); ?></button></form></p> <p><form action="<?php echo $router->generate('users_do', ['id' => 10, 'action' => 'update']); ?>" method="post"><button type="submit"><?php echo $router->generate('users_do', ['id' => 10, 'action' => 'update']); ?></button></form></p>

10
phpcs.xml Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<ruleset name="rules">
<description>rules</description>
<rule ref="PSR2"/>
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<file>tests</file>
<file>AltoRouter.php</file>
<file>examples/</file>
<arg name="colors"/>
</ruleset>

View File

@@ -2,42 +2,49 @@
require 'AltoRouter.php'; require 'AltoRouter.php';
class AltoRouterDebug extends AltoRouter{ class AltoRouterDebug extends AltoRouter
public function getNamedRoutes(){ {
public function getNamedRoutes()
{
return $this->namedRoutes; return $this->namedRoutes;
} }
public function getBasePath(){ public function getBasePath()
{
return $this->basePath; return $this->basePath;
} }
} }
class SimpleTraversable implements Iterator{ class SimpleTraversable implements Iterator
{
protected $_position = 0; protected $_position = 0;
protected $_data = array( protected $_data = [
array('GET', '/foo', 'foo_action', null), ['GET', '/foo', 'foo_action', null],
array('POST', '/bar', 'bar_action', 'second_route') ['POST', '/bar', 'bar_action', 'second_route']
); ];
public function current(){ public function current()
{
return $this->_data[$this->_position]; return $this->_data[$this->_position];
} }
public function key(){ public function key()
{
return $this->_position; return $this->_position;
} }
public function next(){ public function next()
{
++$this->_position; ++$this->_position;
} }
public function rewind(){ public function rewind()
{
$this->_position = 0; $this->_position = 0;
} }
public function valid(){ public function valid()
{
return isset($this->_data[$this->_position]); return isset($this->_data[$this->_position]);
} }
} }
class AltoRouterTest extends PHPUnit\Framework\TestCase class AltoRouterTest extends PHPUnit\Framework\TestCase
@@ -71,12 +78,13 @@ class AltoRouterTest extends PHPUnit\Framework\TestCase
{ {
$method = 'POST'; $method = 'POST';
$route = '/[:controller]/[:action]'; $route = '/[:controller]/[:action]';
$target = function(){}; $target = static function () {
};
$this->assertInternalType('array', $this->router->getRoutes()); $this->assertInternalType('array', $this->router->getRoutes());
// $this->assertIsArray($this->router->getRoutes()); // for phpunit 7.x // $this->assertIsArray($this->router->getRoutes()); // for phpunit 7.x
$this->router->map($method, $route, $target); $this->router->map($method, $route, $target);
$this->assertEquals(array(array($method, $route, $target, null)), $this->router->getRoutes()); $this->assertEquals([[$method, $route, $target, null]], $this->router->getRoutes());
} }
/** /**
@@ -86,17 +94,18 @@ class AltoRouterTest extends PHPUnit\Framework\TestCase
{ {
$method = 'POST'; $method = 'POST';
$route = '/[:controller]/[:action]'; $route = '/[:controller]/[:action]';
$target = function(){}; $target = static function () {
};
$this->router->addRoutes(array( $this->router->addRoutes([
array($method, $route, $target), [$method, $route, $target],
array($method, $route, $target, 'second_route') [$method, $route, $target, 'second_route']
)); ]);
$routes = $this->router->getRoutes(); $routes = $this->router->getRoutes();
$this->assertEquals(array($method, $route, $target, null), $routes[0]); $this->assertEquals([$method, $route, $target, null], $routes[0]);
$this->assertEquals(array($method, $route, $target, 'second_route'), $routes[1]); $this->assertEquals([$method, $route, $target, 'second_route'], $routes[1]);
} }
/** /**
@@ -147,13 +156,14 @@ class AltoRouterTest extends PHPUnit\Framework\TestCase
{ {
$method = 'POST'; $method = 'POST';
$route = '/[:controller]/[:action]'; $route = '/[:controller]/[:action]';
$target = function(){}; $target = static function () {
};
$this->router->map($method, $route, $target); $this->router->map($method, $route, $target);
$routes = $this->router->getRoutes(); $routes = $this->router->getRoutes();
$this->assertEquals(array($method, $route, $target, null), $routes[0]); $this->assertEquals([$method, $route, $target, null], $routes[0]);
} }
/** /**
@@ -163,21 +173,22 @@ class AltoRouterTest extends PHPUnit\Framework\TestCase
{ {
$method = 'POST'; $method = 'POST';
$route = '/[:controller]/[:action]'; $route = '/[:controller]/[:action]';
$target = function(){}; $target = static function () {
};
$name = 'myroute'; $name = 'myroute';
$this->router->map($method, $route, $target, $name); $this->router->map($method, $route, $target, $name);
$routes = $this->router->getRoutes(); $routes = $this->router->getRoutes();
$this->assertEquals(array($method, $route, $target, $name), $routes[0]); $this->assertEquals([$method, $route, $target, $name], $routes[0]);
$named_routes = $this->router->getNamedRoutes(); $named_routes = $this->router->getNamedRoutes();
$this->assertEquals($route, $named_routes[$name]); $this->assertEquals($route, $named_routes[$name]);
try{ try {
$this->router->map($method, $route, $target, $name); $this->router->map($method, $route, $target, $name);
$this->fail('Should not be able to add existing named route'); $this->fail('Should not be able to add existing named route');
}catch(Exception $e){ } catch (Exception $e) {
$this->assertEquals("Can not redeclare route '{$name}'", $e->getMessage()); $this->assertEquals("Can not redeclare route '{$name}'", $e->getMessage());
} }
} }
@@ -188,75 +199,96 @@ class AltoRouterTest extends PHPUnit\Framework\TestCase
*/ */
public function testGenerate() public function testGenerate()
{ {
$params = array( $params =[
'controller' => 'test', 'controller' => 'test',
'action' => 'someaction' 'action' => 'someaction'
];
$this->router->map('GET', '/[:controller]/[:action]', static function () {
}, 'foo_route');
$this->assertEquals(
'/test/someaction',
$this->router->generate('foo_route', $params)
); );
$this->router->map('GET', '/[:controller]/[:action]', function(){}, 'foo_route'); $params = [
$this->assertEquals('/test/someaction',
$this->router->generate('foo_route', $params));
$params = array(
'controller' => 'test', 'controller' => 'test',
'action' => 'someaction', 'action' => 'someaction',
'type' => 'json' 'type' => 'json'
];
$this->assertEquals(
'/test/someaction',
$this->router->generate('foo_route', $params)
); );
$this->assertEquals('/test/someaction',
$this->router->generate('foo_route', $params));
} }
/**
* @covers AltoRouter::generate
*/
public function testGenerateWithOptionalUrlParts() public function testGenerateWithOptionalUrlParts()
{ {
$this->router->map('GET', '/[:controller]/[:action].[:type]?', function(){}, 'bar_route'); $this->router->map('GET', '/[:controller]/[:action].[:type]?', static function () {
}, 'bar_route');
$params = array( $params = [
'controller' => 'test', 'controller' => 'test',
'action' => 'someaction' 'action' => 'someaction'
];
$this->assertEquals(
'/test/someaction',
$this->router->generate('bar_route', $params)
); );
$this->assertEquals('/test/someaction', $params = [
$this->router->generate('bar_route', $params));
$params = array(
'controller' => 'test', 'controller' => 'test',
'action' => 'someaction', 'action' => 'someaction',
'type' => 'json' 'type' => 'json'
); ];
$this->assertEquals('/test/someaction.json', $this->assertEquals(
$this->router->generate('bar_route', $params)); '/test/someaction.json',
$this->router->generate('bar_route', $params)
);
} }
/** /**
* GitHub #98 * GitHub #98
* @covers AltoRouter::generate
*/ */
public function testGenerateWithOptionalPartOnBareUrl() public function testGenerateWithOptionalPartOnBareUrl()
{ {
$this->router->map('GET', '/[i:page]?', function(){}, 'bare_route'); $this->router->map('GET', '/[i:page]?', static function () {
}, 'bare_route');
$params = array( $params = [
'page' => 1 'page' => 1
];
$this->assertEquals(
'/1',
$this->router->generate('bare_route', $params)
); );
$this->assertEquals('/1', $params = [];
$this->router->generate('bare_route', $params));
$params = array(); $this->assertEquals(
'/',
$this->assertEquals('/', $this->router->generate('bare_route', $params)
$this->router->generate('bare_route', $params)); );
} }
/**
* @covers AltoRouter::generate
*/
public function testGenerateWithNonexistingRoute() public function testGenerateWithNonexistingRoute()
{ {
try{ try {
$this->router->generate('nonexisting_route'); $this->router->generate('nonexisting_route');
$this->fail('Should trigger an exception on nonexisting named route'); $this->fail('Should trigger an exception on nonexisting named route');
}catch(Exception $e){ } catch (Exception $e) {
$this->assertEquals("Route 'nonexisting_route' does not exist.", $e->getMessage()); $this->assertEquals("Route 'nonexisting_route' does not exist.", $e->getMessage());
} }
} }
@@ -269,64 +301,73 @@ class AltoRouterTest extends PHPUnit\Framework\TestCase
{ {
$this->router->map('GET', '/foo/[:controller]/[:action]', 'foo_action', 'foo_route'); $this->router->map('GET', '/foo/[:controller]/[:action]', 'foo_action', 'foo_route');
$this->assertEquals(array( $this->assertEquals([
'target' => 'foo_action', 'target' => 'foo_action',
'params' => array( 'params' => [
'controller' => 'test', 'controller' => 'test',
'action' => 'do' 'action' => 'do'
), ],
'name' => 'foo_route' 'name' => 'foo_route'
), $this->router->match('/foo/test/do', 'GET')); ], $this->router->match('/foo/test/do', 'GET'));
$this->assertFalse($this->router->match('/foo/test/do', 'POST')); $this->assertFalse($this->router->match('/foo/test/do', 'POST'));
$this->assertEquals(array( $this->assertEquals([
'target' => 'foo_action', 'target' => 'foo_action',
'params' => array( 'params' => [
'controller' => 'test', 'controller' => 'test',
'action' => 'do' 'action' => 'do'
), ],
'name' => 'foo_route' 'name' => 'foo_route'
), $this->router->match('/foo/test/do?param=value', 'GET')); ], $this->router->match('/foo/test/do?param=value', 'GET'));
} }
public function testMatchWithNonRegex() { /**
$this->router->map('GET','/about-us', 'PagesController#about', 'about_us'); * @covers AltoRouter::match
*/
public function testMatchWithNonRegex()
{
$this->router->map('GET', '/about-us', 'PagesController#about', 'about_us');
$this->assertEquals(array( $this->assertEquals([
'target' => 'PagesController#about', 'target' => 'PagesController#about',
'params' => array(), 'params' => [],
'name' => 'about_us' 'name' => 'about_us'
), $this->router->match('/about-us', 'GET')); ], $this->router->match('/about-us', 'GET'));
$this->assertFalse($this->router->match('/about-us', 'POST')); $this->assertFalse($this->router->match('/about-us', 'POST'));
$this->assertFalse($this->router->match('/about', 'GET')); $this->assertFalse($this->router->match('/about', 'GET'));
$this->assertFalse($this->router->match('/about-us-again', 'GET')); $this->assertFalse($this->router->match('/about-us-again', 'GET'));
} }
/**
* @covers AltoRouter::match
*/
public function testMatchWithFixedParamValues() public function testMatchWithFixedParamValues()
{ {
$this->router->map('POST','/users/[i:id]/[delete|update:action]', 'usersController#doAction', 'users_do'); $this->router->map('POST', '/users/[i:id]/[delete|update:action]', 'usersController#doAction', 'users_do');
$this->assertEquals(array( $this->assertEquals([
'target' => 'usersController#doAction', 'target' => 'usersController#doAction',
'params' => array( 'params' => [
'id' => 1, 'id' => 1,
'action' => 'delete' 'action' => 'delete'
), ],
'name' => 'users_do' 'name' => 'users_do'
), $this->router->match('/users/1/delete', 'POST')); ], $this->router->match('/users/1/delete', 'POST'));
$this->assertFalse($this->router->match('/users/1/delete', 'GET')); $this->assertFalse($this->router->match('/users/1/delete', 'GET'));
$this->assertFalse($this->router->match('/users/abc/delete', 'POST')); $this->assertFalse($this->router->match('/users/abc/delete', 'POST'));
$this->assertFalse($this->router->match('/users/1/create', 'GET')); $this->assertFalse($this->router->match('/users/1/create', 'GET'));
} }
/**
* @covers AltoRouter::match
*/
public function testMatchWithPlainRoute() public function testMatchWithPlainRoute()
{ {
$router = $this->getMockBuilder('AltoRouterDebug') $router = $this->getMockBuilder('AltoRouterDebug')
->setMethods(array('compileRoute')) ->setMethods(['compileRoute'])
->getMock(); ->getMock();
// this should prove that compileRoute is not called when the route doesn't // this should prove that compileRoute is not called when the route doesn't
@@ -337,19 +378,21 @@ class AltoRouterTest extends PHPUnit\Framework\TestCase
$router->map('GET', '/contact', 'website#contact', 'contact'); $router->map('GET', '/contact', 'website#contact', 'contact');
// exact match, so no regex compilation necessary // exact match, so no regex compilation necessary
$this->assertEquals(array( $this->assertEquals([
'target' => 'website#contact', 'target' => 'website#contact',
'params' => array(), 'params' => [],
'name' => 'contact' 'name' => 'contact'
), $router->match('/contact', 'GET')); ], $router->match('/contact', 'GET'));
$router->map('GET', '/page/[:id]', 'pages#show', 'page'); $router->map('GET', '/page/[:id]', 'pages#show', 'page');
// no prefix match, so no regex compilation necessary // no prefix match, so no regex compilation necessary
$this->assertFalse($router->match('/page1', 'GET')); $this->assertFalse($router->match('/page1', 'GET'));
} }
/**
* @covers AltoRouter::match
*/
public function testMatchWithServerVars() public function testMatchWithServerVars()
{ {
$this->router->map('GET', '/foo/[:controller]/[:action]', 'foo_action', 'foo_route'); $this->router->map('GET', '/foo/[:controller]/[:action]', 'foo_action', 'foo_route');
@@ -357,88 +400,98 @@ class AltoRouterTest extends PHPUnit\Framework\TestCase
$_SERVER['REQUEST_URI'] = '/foo/test/do'; $_SERVER['REQUEST_URI'] = '/foo/test/do';
$_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REQUEST_METHOD'] = 'GET';
$this->assertEquals(array( $this->assertEquals([
'target' => 'foo_action', 'target' => 'foo_action',
'params' => array( 'params' => [
'controller' => 'test', 'controller' => 'test',
'action' => 'do' 'action' => 'do'
), ],
'name' => 'foo_route' 'name' => 'foo_route'
), $this->router->match()); ], $this->router->match());
} }
/**
* @covers AltoRouter::match
*/
public function testMatchWithOptionalUrlParts() public function testMatchWithOptionalUrlParts()
{ {
$this->router->map('GET', '/bar/[:controller]/[:action].[:type]?', 'bar_action', 'bar_route'); $this->router->map('GET', '/bar/[:controller]/[:action].[:type]?', 'bar_action', 'bar_route');
$this->assertEquals(array( $this->assertEquals([
'target' => 'bar_action', 'target' => 'bar_action',
'params' => array( 'params' => [
'controller' => 'test', 'controller' => 'test',
'action' => 'do', 'action' => 'do',
'type' => 'json' 'type' => 'json'
), ],
'name' => 'bar_route' 'name' => 'bar_route'
), $this->router->match('/bar/test/do.json', 'GET')); ], $this->router->match('/bar/test/do.json', 'GET'));
$this->assertEquals(array( $this->assertEquals([
'target' => 'bar_action', 'target' => 'bar_action',
'params' => array( 'params' => [
'controller' => 'test', 'controller' => 'test',
'action' => 'do' 'action' => 'do'
), ],
'name' => 'bar_route' 'name' => 'bar_route'
), $this->router->match('/bar/test/do', 'GET')); ], $this->router->match('/bar/test/do', 'GET'));
} }
/** /**
* GitHub #98 * GitHub #98
* @covers AltoRouter::match
*/ */
public function testMatchWithOptionalPartOnBareUrl(){ public function testMatchWithOptionalPartOnBareUrl()
{
$this->router->map('GET', '/[i:page]?', 'bare_action', 'bare_route'); $this->router->map('GET', '/[i:page]?', 'bare_action', 'bare_route');
$this->assertEquals(array( $this->assertEquals([
'target' => 'bare_action', 'target' => 'bare_action',
'params' => array( 'params' => [
'page' => 1 'page' => 1
), ],
'name' => 'bare_route' 'name' => 'bare_route'
), $this->router->match('/1', 'GET')); ], $this->router->match('/1', 'GET'));
$this->assertEquals(array( $this->assertEquals([
'target' => 'bare_action', 'target' => 'bare_action',
'params' => array(), 'params' => [],
'name' => 'bare_route' 'name' => 'bare_route'
), $this->router->match('/', 'GET')); ], $this->router->match('/', 'GET'));
} }
/**
* @covers AltoRouter::match
*/
public function testMatchWithWildcard() public function testMatchWithWildcard()
{ {
$this->router->map('GET', '/a', 'foo_action', 'foo_route'); $this->router->map('GET', '/a', 'foo_action', 'foo_route');
$this->router->map('GET', '*', 'bar_action', 'bar_route'); $this->router->map('GET', '*', 'bar_action', 'bar_route');
$this->assertEquals(array( $this->assertEquals([
'target' => 'bar_action', 'target' => 'bar_action',
'params' => array(), 'params' => [],
'name' => 'bar_route' 'name' => 'bar_route'
), $this->router->match('/everything', 'GET')); ], $this->router->match('/everything', 'GET'));
} }
/**
* @covers AltoRouter::match
*/
public function testMatchWithCustomRegexp() public function testMatchWithCustomRegexp()
{ {
$this->router->map('GET', '@^/[a-z]*$', 'bar_action', 'bar_route'); $this->router->map('GET', '@^/[a-z]*$', 'bar_action', 'bar_route');
$this->assertEquals(array( $this->assertEquals([
'target' => 'bar_action', 'target' => 'bar_action',
'params' => array(), 'params' => [],
'name' => 'bar_route' 'name' => 'bar_route'
), $this->router->match('/everything', 'GET')); ], $this->router->match('/everything', 'GET'));
$this->assertFalse($this->router->match('/some-other-thing', 'GET')); $this->assertFalse($this->router->match('/some-other-thing', 'GET'));
} }
/**
* @covers AltoRouter::match
*/
public function testMatchWithUnicodeRegex() public function testMatchWithUnicodeRegex()
{ {
$pattern = '/(?<path>[^'; $pattern = '/(?<path>[^';
@@ -455,13 +508,13 @@ class AltoRouterTest extends PHPUnit\Framework\TestCase
$this->router->map('GET', '@' . $pattern, 'unicode_action', 'unicode_route'); $this->router->map('GET', '@' . $pattern, 'unicode_action', 'unicode_route');
$this->assertEquals(array( $this->assertEquals([
'target' => 'unicode_action', 'target' => 'unicode_action',
'name' => 'unicode_route', 'name' => 'unicode_route',
'params' => array( 'params' => [
'path' => '大家好' 'path' => '大家好'
) ]
), $this->router->match('/大家好', 'GET')); ], $this->router->match('/大家好', 'GET'));
$this->assertFalse($this->router->match('/﷽‎', 'GET')); $this->assertFalse($this->router->match('/﷽‎', 'GET'));
} }
@@ -471,29 +524,30 @@ class AltoRouterTest extends PHPUnit\Framework\TestCase
*/ */
public function testMatchWithCustomNamedRegex() public function testMatchWithCustomNamedRegex()
{ {
$this->router->addMatchTypes(array('cId' => '[a-zA-Z]{2}[0-9](?:_[0-9]++)?')); $this->router->addMatchTypes(['cId' => '[a-zA-Z]{2}[0-9](?:_[0-9]++)?']);
$this->router->map('GET', '/bar/[cId:customId]', 'bar_action', 'bar_route'); $this->router->map('GET', '/bar/[cId:customId]', 'bar_action', 'bar_route');
$this->assertEquals(array( $this->assertEquals([
'target' => 'bar_action', 'target' => 'bar_action',
'params' => array( 'params' => [
'customId' => 'AB1', 'customId' => 'AB1',
), ],
'name' => 'bar_route' 'name' => 'bar_route'
), $this->router->match('/bar/AB1', 'GET')); ], $this->router->match('/bar/AB1', 'GET'));
$this->assertEquals(array( $this->assertEquals([
'target' => 'bar_action', 'target' => 'bar_action',
'params' => array( 'params' => [
'customId' => 'AB1_0123456789', 'customId' => 'AB1_0123456789',
), ],
'name' => 'bar_route' 'name' => 'bar_route'
), $this->router->match('/bar/AB1_0123456789', 'GET')); ], $this->router->match('/bar/AB1_0123456789', 'GET'));
$this->assertFalse($this->router->match('/some-other-thing', 'GET')); $this->assertFalse($this->router->match('/some-other-thing', 'GET'));
} }
/**
* @covers AltoRouter::addMatchTypes
*/
public function testMatchWithCustomNamedUnicodeRegex() public function testMatchWithCustomNamedUnicodeRegex()
{ {
$pattern = '[^'; $pattern = '[^';
@@ -504,16 +558,16 @@ class AltoRouterTest extends PHPUnit\Framework\TestCase
$pattern .= '\x{0750}-\x{077F}'; $pattern .= '\x{0750}-\x{077F}';
$pattern .= ']+'; $pattern .= ']+';
$this->router->addMatchTypes(array('nonArabic' => $pattern)); $this->router->addMatchTypes(['nonArabic' => $pattern]);
$this->router->map('GET', '/bar/[nonArabic:string]', 'non_arabic_action', 'non_arabic_route'); $this->router->map('GET', '/bar/[nonArabic:string]', 'non_arabic_action', 'non_arabic_route');
$this->assertEquals(array( $this->assertEquals([
'target' => 'non_arabic_action', 'target' => 'non_arabic_action',
'name' => 'non_arabic_route', 'name' => 'non_arabic_route',
'params' => array( 'params' => [
'string' => 'some-path' 'string' => 'some-path'
) ]
), $this->router->match('/bar/some-path', 'GET')); ], $this->router->match('/bar/some-path', 'GET'));
$this->assertFalse($this->router->match('/﷽‎', 'GET')); $this->assertFalse($this->router->match('/﷽‎', 'GET'));
} }

View File

@@ -14,10 +14,11 @@
require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/../vendor/autoload.php';
global $argv; global $argv;
$n = isset( $argv[1] ) ? intval( $argv[1] ) : 1000; $n = isset($argv[1]) ? intval($argv[1]) : 1000;
// generates a random request url // generates a random request url
function random_request_url() { function random_request_url()
{
$characters = 'abcdefghijklmnopqrstuvwxyz'; $characters = 'abcdefghijklmnopqrstuvwxyz';
$charactersLength = strlen($characters); $charactersLength = strlen($characters);
$randomString = '/'; $randomString = '/';
@@ -26,45 +27,46 @@ function random_request_url() {
for ($i = 0; $i < rand(5, 20); $i++) { for ($i = 0; $i < rand(5, 20); $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)]; $randomString .= $characters[rand(0, $charactersLength - 1)];
if( rand(1, 10) === 1 ) { if (rand(1, 10) === 1) {
$randomString .= '/'; $randomString .= '/';
} }
} }
// add dynamic route with 10% chance // add dynamic route with 10% chance
if ( rand(1, 10) === 1 ) { if (rand(1, 10) === 1) {
$randomString = rtrim( $randomString, '/' ) . '/[:part]'; $randomString = rtrim($randomString, '/') . '/[:part]';
} }
return $randomString; return $randomString;
} }
// generate a random request method // generate a random request method
function random_request_method() { function random_request_method()
static $methods = array( 'GET', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE' ); {
$random_key = array_rand( $methods ); static $methods = [ 'GET', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE' ];
$random_key = array_rand($methods);
return $methods[ $random_key ]; return $methods[ $random_key ];
} }
// prepare benchmark data // prepare benchmark data
$requests = array(); $requests = [];
for($i=0; $i<$n; $i++) { for ($i=0; $i<$n; $i++) {
$requests[] = array( $requests[] = [
'method' => random_request_method(), 'method' => random_request_method(),
'url' => random_request_url(), 'url' => random_request_url(),
); ];
} }
$router = new AltoRouter(); $router = new AltoRouter();
// map requests // map requests
$start = microtime(true); $start = microtime(true);
foreach($requests as $r) { foreach ($requests as $r) {
$router->map($r['method'], $r['url'], ''); $router->map($r['method'], $r['url'], '');
} }
$end = microtime(true); $end = microtime(true);
$map_time = ($end - $start) * 1000; $map_time = ($end - $start) * 1000;
echo sprintf( 'Map time: %.2f ms', $map_time ) . PHP_EOL; echo sprintf('Map time: %.2f ms', $map_time) . PHP_EOL;
// pick random route to match // pick random route to match
@@ -75,19 +77,16 @@ $start = microtime(true);
$router->match($r['url'], $r['method']); $router->match($r['url'], $r['method']);
$end = microtime(true); $end = microtime(true);
$match_time_known_route = ($end - $start) * 1000; $match_time_known_route = ($end - $start) * 1000;
echo sprintf( 'Match time (known route): %.2f ms', $match_time_known_route ) . PHP_EOL; echo sprintf('Match time (known route): %.2f ms', $match_time_known_route) . PHP_EOL;
// match unexisting route // match unexisting route
$start = microtime(true); $start = microtime(true);
$router->match('/55-foo-bar', 'GET'); $router->match('/55-foo-bar', 'GET');
$end = microtime(true); $end = microtime(true);
$match_time_unknown_route = ($end - $start) * 1000; $match_time_unknown_route = ($end - $start) * 1000;
echo sprintf( 'Match time (unknown route): %.2f ms', $match_time_unknown_route ) . PHP_EOL; echo sprintf('Match time (unknown route): %.2f ms', $match_time_unknown_route) . PHP_EOL;
// print totals // print totals
echo sprintf('Total time: %.2f seconds', ($map_time + $match_time_known_route + $match_time_unknown_route)) . PHP_EOL; echo sprintf('Total time: %.2f seconds', ($map_time + $match_time_known_route + $match_time_unknown_route)) . PHP_EOL;
echo sprintf('Memory usage: %d KB', round( memory_get_usage() / 1024 )) . PHP_EOL; echo sprintf('Memory usage: %d KB', round(memory_get_usage() / 1024)) . PHP_EOL;
echo sprintf('Peak memory usage: %d KB', round( memory_get_peak_usage( true ) / 1024 )) . PHP_EOL; echo sprintf('Peak memory usage: %d KB', round(memory_get_peak_usage(true) / 1024)) . PHP_EOL;