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

29 Commits
v1.2.0 ... 2.0

Author SHA1 Message Date
Danny van Kooten
7e2b48db00 type-hint array in constructor parameter too 2019-11-10 20:33:12 +01:00
Danny van Kooten
3a97b2c432 use short array notation in readme too 2019-11-10 20:32:52 +01:00
Danny van Kooten
98e97407de use vendored phpunit in travis 2019-11-10 20:27:38 +01:00
Danny van Kooten
17d00ed90e update readme 2019-11-10 20:26:36 +01:00
Danny van Kooten
a34caaf60c 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
2019-11-10 20:18:39 +01:00
Félix Dorn
a01166760c phpcs formatting 2019-07-03 21:44:06 +02:00
Félix Dorn
cd918de64b add @covers on every test, remove array long syntax 2019-07-03 21:43:47 +02:00
Félix Dorn
2360f527b0 add type hinting, removed array long syntax 2019-07-03 21:40:17 +02:00
Félix Dorn
24360d6162 add php-cs to match with PSR2 requirements 2019-07-03 21:39:35 +02:00
Danny van Kooten
dce6efdea2 fix phpunit on older php versions 2019-02-07 09:52:34 +01:00
Danny van Kooten
ecb5f69042 use travis phpunit so that we can test on lower php versions too 2019-02-07 09:44:18 +01:00
Danny van Kooten
9e9767e8fa add support for php's built-in web server to example. #226 2019-02-07 09:38:22 +01:00
Danny van Kooten
87d93b6840 update benchmark 2019-02-07 09:16:23 +01:00
Danny van Kooten
9bdfc55eb3 fix tests by making compileRoute protected instead 2019-02-07 09:16:13 +01:00
Danny van Kooten
df96d7270c update to phpunit 7.5 2019-02-06 20:13:21 +01:00
Danny van Kooten
e2a07ec452 test recent php versions, stop testing 5.3-5.5 2019-02-06 20:01:07 +01:00
Danny van Kooten
ca2d425bc6 add example for url generation to readme 2018-08-19 11:51:01 +02:00
Danny van Kooten
281c5631db add license to main php file too 2018-08-19 11:47:59 +02:00
Danny van Kooten
58299f4d0d add license file 2018-08-19 11:47:09 +02:00
Danny van Kooten
1ff4165b28 improve benchmark by preparing first, matching & only mapping two routes 2017-04-10 16:36:22 +02:00
Koen Punt
689670f4dc Merge pull request #99 from koenpunt/bare-optional-parameter
Allow for optional parameters on bare/root url
2017-04-04 20:37:25 +02:00
Koen Punt
72dd5199bd multi optional 2017-04-02 20:41:58 +02:00
Koen Punt
be64536dcb prevent strip of preceding slash at base 2017-04-02 20:41:10 +02:00
Koen Punt
f18a57d481 add failing test for generate 2016-12-30 00:11:18 +01:00
Koen Punt
e72248c744 add failing test for #98 2016-12-30 00:11:18 +01:00
Koen Punt
693752b77a simplify loop for non-param substring (#131)
optimize comparison using strcmp functions
2016-06-21 23:27:25 +02:00
Danny van Kooten
a8ee8e875f Merge pull request #130 from dannyvankooten/strpos-request-method
Use `stripos` to compare request method (instead of `explode` + `in_array`) for improved performance.
2016-06-21 22:37:56 +02:00
Danny van Kooten
48524b67cb Add benchmark script to tests directory for easy benchmarking. 2016-06-21 15:27:21 +02:00
Danny van Kooten
6fe6f4b196 Use stripos to compare request method, instead of explode + in_array. 2015-11-03 08:13:51 +07:00
10 changed files with 971 additions and 719 deletions

View File

@@ -1,17 +1,16 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
- 7.1
- 7.2
- 7.3
install:
- composer update --prefer-source
script:
- ./vendor/bin/phpunit
- vendor/bin/phpunit
after_script:
- ./vendor/bin/test-reporter
- vendor/bin/test-reporter
env:
global:
secure: XnXSc7nxJMIrm/EJ1KuwlN4f+sj2R/sR0IFHdOdbOfMKyZ/u6WEgZ3vNrdeAsisiC/QUJJ00DGku1pAl3t3Hzvam0N/SiHtXjB1ZLVbX00S1PEZ6Z+h9zoaUBXWoN6+0OdKN0Xjmj2lwvTpvUxUZXNabilOw0F9WS/+JasofqBQ=

View File

@@ -1,16 +1,28 @@
<?php
/*
MIT License
class AltoRouter {
Copyright (c) 2012 Danny van Kooten <hi@dannyvankooten.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of 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
{
/**
* @var array Array of all routes (incl. named routes).
*/
protected $routes = array();
protected $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)
@@ -20,14 +32,14 @@ class AltoRouter {
/**
* @var array Array of default match types (regex helpers)
*/
protected $matchTypes = array(
protected $matchTypes = [
'i' => '[0-9]++',
'a' => '[0-9A-Za-z]++',
'h' => '[0-9A-Fa-f]++',
'*' => '.+?',
'**' => '.++',
'' => '[^/\.]++'
);
];
/**
* Create router in one call from config.
@@ -35,8 +47,10 @@ class AltoRouter {
* @param array $routes
* @param string $basePath
* @param array $matchTypes
* @throws Exception
*/
public function __construct( $routes = array(), $basePath = '', $matchTypes = array() ) {
public function __construct(array $routes = [], $basePath = '', array $matchTypes = [])
{
$this->addRoutes($routes);
$this->setBasePath($basePath);
$this->addMatchTypes($matchTypes);
@@ -47,36 +61,40 @@ class AltoRouter {
* Useful if you want to process or display routes.
* @return array All routes.
*/
public function getRoutes() {
public function getRoutes()
{
return $this->routes;
}
/**
* Add multiple routes at once from array in the following format:
*
* $routes = array(
* array($method, $route, $target, $name)
* );
* $routes = [
* [$method, $route, $target, $name]
* ];
*
* @param array $routes
* @return void
* @author Koen Punt
* @throws Exception
*/
public function addRoutes($routes){
if(!is_array($routes) && !$routes instanceof Traversable) {
throw new \Exception('Routes should be an array or an instance of Traversable');
public function addRoutes($routes)
{
if (!is_array($routes) && !$routes instanceof Traversable) {
throw new RuntimeException('Routes should be an array or an instance of Traversable');
}
foreach($routes as $route) {
call_user_func_array(array($this, 'map'), $route);
foreach ($routes as $route) {
call_user_func_array([$this, 'map'], $route);
}
}
/**
* Set the base path.
* Useful if you are running your application from a subdirectory.
* @param string $basePath
*/
public function setBasePath($basePath) {
public function setBasePath($basePath)
{
$this->basePath = $basePath;
}
@@ -85,7 +103,8 @@ class AltoRouter {
*
* @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);
}
@@ -98,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.
* @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(isset($this->namedRoutes[$name])) {
throw new \Exception("Can not redeclare route '{$name}'");
} else {
$this->namedRoutes[$name] = $route;
if ($name) {
if (isset($this->namedRoutes[$name])) {
throw new RuntimeException("Can not redeclare route '{$name}'");
}
$this->namedRoutes[$name] = $route;
}
return;
@@ -124,11 +142,12 @@ class AltoRouter {
* @return string The URL of the route with named parameters in place.
* @throws Exception
*/
public function generate($routeName, array $params = array()) {
public function generate($routeName, array $params = [])
{
// Check if named route exists
if(!isset($this->namedRoutes[$routeName])) {
throw new \Exception("Route '{$routeName}' does not exist.");
if (!isset($this->namedRoutes[$routeName])) {
throw new RuntimeException("Route '{$routeName}' does not exist.");
}
// Replace named parameters
@@ -138,22 +157,24 @@ class AltoRouter {
$url = $this->basePath . $route;
if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
foreach($matches as $match) {
foreach ($matches as $index => $match) {
list($block, $pre, $type, $param, $optional) = $match;
if ($pre) {
$block = substr($block, 1);
}
if(isset($params[$param])) {
if (isset($params[$param])) {
// Part is found, replace for param value
$url = str_replace($block, $params[$param], $url);
} elseif ($optional) {
} elseif ($optional && $index !== 0) {
// Only strip preceding slash if it's not at the base
$url = str_replace($pre . $block, '', $url);
} else {
// Strip match block
$url = str_replace($block, '', $url);
}
}
}
return $url;
@@ -165,13 +186,13 @@ class AltoRouter {
* @param string $requestMethod
* @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();
$match = false;
$params = [];
// set Request Url if it isn't passed as parameter
if($requestUrl === null) {
if ($requestUrl === null) {
$requestUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
}
@@ -184,76 +205,53 @@ class AltoRouter {
}
// 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';
}
foreach($this->routes as $handler) {
list($method, $_route, $target, $name) = $handler;
foreach ($this->routes as $handler) {
list($methods, $route, $target, $name) = $handler;
$methods = explode('|', $method);
$method_match = false;
// Check if request method matches. If not, abandon early. (CHEAP)
foreach($methods as $method) {
if (strcasecmp($requestMethod, $method) === 0) {
$method_match = true;
break;
}
}
$method_match = (stripos($methods, $requestMethod) !== false);
// Method did not match, continue to next route.
if(!$method_match) continue;
if (!$method_match) {
continue;
}
// Check for a wildcard (matches all)
if ($_route === '*') {
if ($route === '*') {
// * wildcard (matches all)
$match = true;
} elseif (isset($_route[0]) && $_route[0] === '@') {
$pattern = '`' . substr($_route, 1) . '`u';
$match = preg_match($pattern, $requestUrl, $params);
} elseif (isset($route[0]) && $route[0] === '@') {
// @ regex delimiter
$pattern = '`' . substr($route, 1) . '`u';
$match = preg_match($pattern, $requestUrl, $params) === 1;
} elseif (($position = strpos($route, '[')) === false) {
// No params in url, do string comparison
$match = strcmp($requestUrl, $route) === 0;
} else {
$route = null;
$regex = false;
$j = 0;
$n = isset($_route[0]) ? $_route[0] : null;
$i = 0;
// Find the longest non-regex substring and match it against the URI
while (true) {
if (!isset($_route[$i])) {
break;
} elseif (false === $regex) {
$c = $n;
$regex = $c === '[' || $c === '(' || $c === '.';
if (false === $regex && false !== isset($_route[$i+1])) {
$n = $_route[$i + 1];
$regex = $n === '?' || $n === '+' || $n === '*' || $n === '{';
// Compare longest non-param string with url
if (strncmp($requestUrl, $route, $position) !== 0) {
continue;
}
if (false === $regex && $c !== '/' && (!isset($requestUrl[$j]) || $c !== $requestUrl[$j])) {
continue 2;
}
$j++;
}
$route .= $_route[$i++];
}
$regex = $this->compileRoute($route);
$match = preg_match($regex, $requestUrl, $params);
$match = preg_match($regex, $requestUrl, $params) === 1;
}
if(($match == true || $match > 0)) {
if($params) {
foreach($params as $key => $value) {
if(is_numeric($key)) unset($params[$key]);
if ($match) {
if ($params) {
foreach ($params as $key => $value) {
if (is_numeric($key)) {
unset($params[$key]);
}
}
}
return array(
return [
'target' => $target,
'params' => $params,
'name' => $name
);
];
}
}
return false;
@@ -261,12 +259,14 @@ class AltoRouter {
/**
* Compile the regex for a given route (EXPENSIVE)
* @param $route
* @return string
*/
private function compileRoute($route) {
protected function compileRoute($route)
{
if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
$matchTypes = $this->matchTypes;
foreach($matches as $match) {
foreach ($matches as $match) {
list($block, $pre, $type, $param, $optional) = $match;
if (isset($matchTypes[$type])) {
@@ -276,18 +276,21 @@ class AltoRouter {
$pre = '\.';
}
$optional = $optional !== '' ? '?' : null;
//Older versions of PCRE require the 'P' in (?P<named>)
$pattern = '(?:'
. ($pre !== '' ? $pre : null)
. '('
. ($param !== '' ? "?P<$param>" : null)
. $type
. '))'
. ($optional !== '' ? '?' : null);
. ')'
. $optional
. ')'
. $optional;
$route = str_replace($block, $pattern, $route);
}
}
return "`^$route$`u";
}

9
LICENSE.md Normal file
View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) 2012 Danny van Kooten <hi@dannyvankooten.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of 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.

View File

@@ -1,19 +1,22 @@
# AltoRouter [![Build Status](https://api.travis-ci.org/dannyvankooten/AltoRouter.png)](http://travis-ci.org/dannyvankooten/AltoRouter) [![Latest Stable Version](https://poser.pugx.org/altorouter/altorouter/v/stable.svg)](https://packagist.org/packages/altorouter/altorouter) [![License](https://poser.pugx.org/altorouter/altorouter/license.svg)](https://packagist.org/packages/altorouter/altorouter) [![Code Climate](https://codeclimate.com/github/dannyvankooten/AltoRouter/badges/gpa.svg)](https://codeclimate.com/github/dannyvankooten/AltoRouter) [![Test Coverage](https://codeclimate.com/github/dannyvankooten/AltoRouter/badges/coverage.svg)](https://codeclimate.com/github/dannyvankooten/AltoRouter)
AltoRouter is a small but powerful routing class for PHP 5.3+, heavily inspired by [klein.php](https://github.com/chriso/klein.php/).
AltoRouter is a small but powerful routing class, heavily inspired by [klein.php](https://github.com/chriso/klein.php/).
```php
$router = new AltoRouter();
// map homepage
$router->map( 'GET', '/', function() {
$router->map('GET', '/', function() {
require __DIR__ . '/views/home.php';
});
// map users details page
$router->map( 'GET|POST', '/users/[i:id]/', function( $id ) {
// dynamic named route
$router->map('GET|POST', '/users/[i:id]/', function($id) {
$user = .....
require __DIR__ . '/views/user/details.php';
});
}, 'user-details');
// echo URL to user-details page for ID 5
echo $router->generate('user-details', ['id' => 5]); // Output: "/users/5"
```
## Features
@@ -26,7 +29,7 @@ $router->map( 'GET|POST', '/users/[i:id]/', function( $id ) {
## Getting started
You need PHP >= 5.3 to use AltoRouter.
You need PHP >= 5.6 to use AltoRouter, although we highly recommend you [use an officially supported PHP version](https://secure.php.net/supported-versions.php) that is not EOL.
- [Install AltoRouter](http://altorouter.com/usage/install.html)
- [Rewrite all requests to AltoRouter](http://altorouter.com/usage/rewrite-requests.html)
@@ -42,9 +45,9 @@ You need PHP >= 5.3 to use AltoRouter.
## License
(MIT License)
MIT License
Copyright (c) 2012-2015 Danny van Kooten <hi@dannyvankooten.com>
Copyright (c) 2012 Danny van Kooten <hi@dannyvankooten.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@@ -20,11 +20,12 @@
}
],
"require": {
"php": ">=5.3.0"
"php": ">=5.6.0"
},
"require-dev": {
"phpunit/phpunit": "4.5.*",
"codeclimate/php-test-reporter": "dev-master"
"phpunit/phpunit": "5.7.*",
"codeclimate/php-test-reporter": "dev-master",
"squizlabs/php_codesniffer": "3.4.2"
},
"autoload": {
"classmap": ["AltoRouter.php"]

View File

@@ -1,13 +1,21 @@
<?php
require '../../AltoRouter.php';
require __DIR__ . '/../../AltoRouter.php';
/**
* This can be useful if you're using PHP's built-in web server, to serve files like images or css
* @link https://secure.php.net/manual/en/features.commandline.webserver.php
*/
if (file_exists($_SERVER['SCRIPT_FILENAME']) && pathinfo($_SERVER['SCRIPT_FILENAME'], PATHINFO_EXTENSION) !== 'php') {
return;
}
$router = new AltoRouter();
$router->setBasePath('/AltoRouter/examples/basic');
$router->map('GET|POST','/', 'home#index', 'home');
$router->map('GET','/users/', array('c' => 'UserController', 'a' => 'ListAction'));
$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('GET|POST', '/', 'home#index', 'home');
$router->map('GET', '/users/', ['c' => 'UserController', 'a' => 'ListAction']);
$router->map('GET', '/users/[i:id]', 'users#show', 'users_show');
$router->map('POST', '/users/[i:id]/[delete|update:action]', 'usersController#doAction', 'users_do');
// match current request
$match = $router->match();
@@ -23,5 +31,5 @@ $match = $router->match();
<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('users_show', array('id' => 5)); ?>">GET <?php echo $router->generate('users_show', array('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><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', ['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

@@ -7,9 +7,9 @@
</testsuite>
</testsuites>
<filter>
<blacklist>
<directory>./vendor/</directory>
</blacklist>
<whitelist>
<file>./AltoRouter.php</file>
</whitelist>
</filter>
<logging>
<log type="coverage-clover" target="build/logs/clover.xml"/>

View File

@@ -2,49 +2,52 @@
require 'AltoRouter.php';
class AltoRouterDebug extends AltoRouter{
public function getNamedRoutes(){
class AltoRouterDebug extends AltoRouter
{
public function getNamedRoutes()
{
return $this->namedRoutes;
}
public function getBasePath(){
public function getBasePath()
{
return $this->basePath;
}
}
class SimpleTraversable implements Iterator{
class SimpleTraversable implements Iterator
{
protected $_position = 0;
protected $_data = array(
array('GET', '/foo', 'foo_action', null),
array('POST', '/bar', 'bar_action', 'second_route')
);
protected $_data = [
['GET', '/foo', 'foo_action', null],
['POST', '/bar', 'bar_action', 'second_route']
];
public function current(){
public function current()
{
return $this->_data[$this->_position];
}
public function key(){
public function key()
{
return $this->_position;
}
public function next(){
public function next()
{
++$this->_position;
}
public function rewind(){
public function rewind()
{
$this->_position = 0;
}
public function valid(){
public function valid()
{
return isset($this->_data[$this->_position]);
}
}
/**
* Generated by PHPUnit_SkeletonGenerator 1.2.1 on 2013-07-14 at 17:47:46.
*/
class AltoRouterTest extends PHPUnit_Framework_TestCase
class AltoRouterTest extends PHPUnit\Framework\TestCase
{
/**
* @var AltoRouter
@@ -75,11 +78,13 @@ class AltoRouterTest extends PHPUnit_Framework_TestCase
{
$method = 'POST';
$route = '/[:controller]/[:action]';
$target = function(){};
$target = static function () {
};
$this->assertInternalType('array', $this->router->getRoutes());
// $this->assertIsArray($this->router->getRoutes()); // for phpunit 7.x
$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());
}
/**
@@ -89,17 +94,18 @@ class AltoRouterTest extends PHPUnit_Framework_TestCase
{
$method = 'POST';
$route = '/[:controller]/[:action]';
$target = function(){};
$target = static function () {
};
$this->router->addRoutes(array(
array($method, $route, $target),
array($method, $route, $target, 'second_route')
));
$this->router->addRoutes([
[$method, $route, $target],
[$method, $route, $target, 'second_route']
]);
$routes = $this->router->getRoutes();
$this->assertEquals(array($method, $route, $target, null), $routes[0]);
$this->assertEquals(array($method, $route, $target, 'second_route'), $routes[1]);
$this->assertEquals([$method, $route, $target, null], $routes[0]);
$this->assertEquals([$method, $route, $target, 'second_route'], $routes[1]);
}
/**
@@ -150,13 +156,14 @@ class AltoRouterTest extends PHPUnit_Framework_TestCase
{
$method = 'POST';
$route = '/[:controller]/[:action]';
$target = function(){};
$target = static function () {
};
$this->router->map($method, $route, $target);
$routes = $this->router->getRoutes();
$this->assertEquals(array($method, $route, $target, null), $routes[0]);
$this->assertEquals([$method, $route, $target, null], $routes[0]);
}
/**
@@ -166,21 +173,22 @@ class AltoRouterTest extends PHPUnit_Framework_TestCase
{
$method = 'POST';
$route = '/[:controller]/[:action]';
$target = function(){};
$target = static function () {
};
$name = 'myroute';
$this->router->map($method, $route, $target, $name);
$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();
$this->assertEquals($route, $named_routes[$name]);
try{
try {
$this->router->map($method, $route, $target, $name);
$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());
}
}
@@ -191,55 +199,96 @@ class AltoRouterTest extends PHPUnit_Framework_TestCase
*/
public function testGenerate()
{
$params = array(
$params =[
'controller' => 'test',
'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');
$this->assertEquals('/test/someaction',
$this->router->generate('foo_route', $params));
$params = array(
$params = [
'controller' => 'test',
'action' => 'someaction',
'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()
{
$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',
'action' => 'someaction'
];
$this->assertEquals(
'/test/someaction',
$this->router->generate('bar_route', $params)
);
$this->assertEquals('/test/someaction',
$this->router->generate('bar_route', $params));
$params = array(
$params = [
'controller' => 'test',
'action' => 'someaction',
'type' => 'json'
);
];
$this->assertEquals('/test/someaction.json',
$this->router->generate('bar_route', $params));
$this->assertEquals(
'/test/someaction.json',
$this->router->generate('bar_route', $params)
);
}
/**
* GitHub #98
* @covers AltoRouter::generate
*/
public function testGenerateWithOptionalPartOnBareUrl()
{
$this->router->map('GET', '/[i:page]?', static function () {
}, 'bare_route');
$params = [
'page' => 1
];
$this->assertEquals(
'/1',
$this->router->generate('bare_route', $params)
);
$params = [];
$this->assertEquals(
'/',
$this->router->generate('bare_route', $params)
);
}
/**
* @covers AltoRouter::generate
*/
public function testGenerateWithNonexistingRoute()
{
try{
try {
$this->router->generate('nonexisting_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());
}
}
@@ -252,60 +301,98 @@ class AltoRouterTest extends PHPUnit_Framework_TestCase
{
$this->router->map('GET', '/foo/[:controller]/[:action]', 'foo_action', 'foo_route');
$this->assertEquals(array(
$this->assertEquals([
'target' => 'foo_action',
'params' => array(
'params' => [
'controller' => 'test',
'action' => 'do'
),
],
'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->assertEquals(array(
$this->assertEquals([
'target' => 'foo_action',
'params' => array(
'params' => [
'controller' => 'test',
'action' => 'do'
),
],
'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',
'params' => array(),
'params' => [],
'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', 'GET'));
$this->assertFalse($this->router->match('/about-us-again', 'GET'));
}
/**
* @covers AltoRouter::match
*/
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',
'params' => array(
'params' => [
'id' => 1,
'action' => 'delete'
),
],
'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/abc/delete', 'POST'));
$this->assertFalse($this->router->match('/users/1/create', 'GET'));
}
/**
* @covers AltoRouter::match
*/
public function testMatchWithPlainRoute()
{
$router = $this->getMockBuilder('AltoRouterDebug')
->setMethods(['compileRoute'])
->getMock();
// this should prove that compileRoute is not called when the route doesn't
// have any params in it, but this doesn't work because compileRoute is private.
$router->expects($this->never())
->method('compileRoute');
$router->map('GET', '/contact', 'website#contact', 'contact');
// exact match, so no regex compilation necessary
$this->assertEquals([
'target' => 'website#contact',
'params' => [],
'name' => 'contact'
], $router->match('/contact', 'GET'));
$router->map('GET', '/page/[:id]', 'pages#show', 'page');
// no prefix match, so no regex compilation necessary
$this->assertFalse($router->match('/page1', 'GET'));
}
/**
* @covers AltoRouter::match
*/
public function testMatchWithServerVars()
{
$this->router->map('GET', '/foo/[:controller]/[:action]', 'foo_action', 'foo_route');
@@ -313,59 +400,98 @@ class AltoRouterTest extends PHPUnit_Framework_TestCase
$_SERVER['REQUEST_URI'] = '/foo/test/do';
$_SERVER['REQUEST_METHOD'] = 'GET';
$this->assertEquals(array(
$this->assertEquals([
'target' => 'foo_action',
'params' => array(
'params' => [
'controller' => 'test',
'action' => 'do'
),
],
'name' => 'foo_route'
), $this->router->match());
], $this->router->match());
}
/**
* @covers AltoRouter::match
*/
public function testMatchWithOptionalUrlParts()
{
$this->router->map('GET', '/bar/[:controller]/[:action].[:type]?', 'bar_action', 'bar_route');
$this->assertEquals(array(
$this->assertEquals([
'target' => 'bar_action',
'params' => array(
'params' => [
'controller' => 'test',
'action' => 'do',
'type' => 'json'
),
],
'name' => 'bar_route'
), $this->router->match('/bar/test/do.json', 'GET'));
], $this->router->match('/bar/test/do.json', 'GET'));
$this->assertEquals([
'target' => 'bar_action',
'params' => [
'controller' => 'test',
'action' => 'do'
],
'name' => 'bar_route'
], $this->router->match('/bar/test/do', 'GET'));
}
/**
* GitHub #98
* @covers AltoRouter::match
*/
public function testMatchWithOptionalPartOnBareUrl()
{
$this->router->map('GET', '/[i:page]?', 'bare_action', 'bare_route');
$this->assertEquals([
'target' => 'bare_action',
'params' => [
'page' => 1
],
'name' => 'bare_route'
], $this->router->match('/1', 'GET'));
$this->assertEquals([
'target' => 'bare_action',
'params' => [],
'name' => 'bare_route'
], $this->router->match('/', 'GET'));
}
/**
* @covers AltoRouter::match
*/
public function testMatchWithWildcard()
{
$this->router->map('GET', '/a', 'foo_action', 'foo_route');
$this->router->map('GET', '*', 'bar_action', 'bar_route');
$this->assertEquals(array(
$this->assertEquals([
'target' => 'bar_action',
'params' => array(),
'params' => [],
'name' => 'bar_route'
), $this->router->match('/everything', 'GET'));
], $this->router->match('/everything', 'GET'));
}
/**
* @covers AltoRouter::match
*/
public function testMatchWithCustomRegexp()
{
$this->router->map('GET', '@^/[a-z]*$', 'bar_action', 'bar_route');
$this->assertEquals(array(
$this->assertEquals([
'target' => 'bar_action',
'params' => array(),
'params' => [],
'name' => 'bar_route'
), $this->router->match('/everything', 'GET'));
], $this->router->match('/everything', 'GET'));
$this->assertFalse($this->router->match('/some-other-thing', 'GET'));
}
/**
* @covers AltoRouter::match
*/
public function testMatchWithUnicodeRegex()
{
$pattern = '/(?<path>[^';
@@ -382,13 +508,13 @@ class AltoRouterTest extends PHPUnit_Framework_TestCase
$this->router->map('GET', '@' . $pattern, 'unicode_action', 'unicode_route');
$this->assertEquals(array(
$this->assertEquals([
'target' => 'unicode_action',
'name' => 'unicode_route',
'params' => array(
'params' => [
'path' => '大家好'
)
), $this->router->match('/大家好', 'GET'));
]
], $this->router->match('/大家好', 'GET'));
$this->assertFalse($this->router->match('/﷽‎', 'GET'));
}
@@ -398,29 +524,30 @@ class AltoRouterTest extends PHPUnit_Framework_TestCase
*/
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->assertEquals(array(
$this->assertEquals([
'target' => 'bar_action',
'params' => array(
'params' => [
'customId' => 'AB1',
),
],
'name' => 'bar_route'
), $this->router->match('/bar/AB1', 'GET'));
], $this->router->match('/bar/AB1', 'GET'));
$this->assertEquals(array(
$this->assertEquals([
'target' => 'bar_action',
'params' => array(
'params' => [
'customId' => 'AB1_0123456789',
),
],
'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'));
}
/**
* @covers AltoRouter::addMatchTypes
*/
public function testMatchWithCustomNamedUnicodeRegex()
{
$pattern = '[^';
@@ -431,16 +558,16 @@ class AltoRouterTest extends PHPUnit_Framework_TestCase
$pattern .= '\x{0750}-\x{077F}';
$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->assertEquals(array(
$this->assertEquals([
'target' => 'non_arabic_action',
'name' => 'non_arabic_route',
'params' => array(
'params' => [
'string' => 'some-path'
)
), $this->router->match('/bar/some-path', 'GET'));
]
], $this->router->match('/bar/some-path', 'GET'));
$this->assertFalse($this->router->match('/﷽‎', 'GET'));
}

92
tests/benchmark.php Normal file
View File

@@ -0,0 +1,92 @@
<?php
/**
* Benchmark Altorouter
*
* Usage: php ./tests/benchmark.php <iterations>
*
* Options:
*
* <iterations>:
* The number of routes to map & match. Defaults to 1000.
*/
require __DIR__ . '/../vendor/autoload.php';
global $argv;
$n = isset($argv[1]) ? intval($argv[1]) : 1000;
// generates a random request url
function random_request_url()
{
$characters = 'abcdefghijklmnopqrstuvwxyz';
$charactersLength = strlen($characters);
$randomString = '/';
// create random path of 5-20 characters
for ($i = 0; $i < rand(5, 20); $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
if (rand(1, 10) === 1) {
$randomString .= '/';
}
}
// add dynamic route with 10% chance
if (rand(1, 10) === 1) {
$randomString = rtrim($randomString, '/') . '/[:part]';
}
return $randomString;
}
// generate a random request method
function random_request_method()
{
static $methods = [ 'GET', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE' ];
$random_key = array_rand($methods);
return $methods[ $random_key ];
}
// prepare benchmark data
$requests = [];
for ($i=0; $i<$n; $i++) {
$requests[] = [
'method' => random_request_method(),
'url' => random_request_url(),
];
}
$router = new AltoRouter();
// map requests
$start = microtime(true);
foreach ($requests as $r) {
$router->map($r['method'], $r['url'], '');
}
$end = microtime(true);
$map_time = ($end - $start) * 1000;
echo sprintf('Map time: %.2f ms', $map_time) . PHP_EOL;
// pick random route to match
$r = $requests[array_rand($requests)];
// match random known route
$start = microtime(true);
$router->match($r['url'], $r['method']);
$end = microtime(true);
$match_time_known_route = ($end - $start) * 1000;
echo sprintf('Match time (known route): %.2f ms', $match_time_known_route) . PHP_EOL;
// match unexisting route
$start = microtime(true);
$router->match('/55-foo-bar', 'GET');
$end = microtime(true);
$match_time_unknown_route = ($end - $start) * 1000;
echo sprintf('Match time (unknown route): %.2f ms', $match_time_unknown_route) . PHP_EOL;
// print totals
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('Peak memory usage: %d KB', round(memory_get_peak_usage(true) / 1024)) . PHP_EOL;