mirror of
https://github.com/dannyvankooten/AltoRouter.git
synced 2025-08-21 15:51:28 +02:00
Compare commits
70 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7e2b48db00 | ||
|
3a97b2c432 | ||
|
98e97407de | ||
|
17d00ed90e | ||
|
a34caaf60c | ||
|
a01166760c | ||
|
cd918de64b | ||
|
2360f527b0 | ||
|
24360d6162 | ||
|
dce6efdea2 | ||
|
ecb5f69042 | ||
|
9e9767e8fa | ||
|
87d93b6840 | ||
|
9bdfc55eb3 | ||
|
df96d7270c | ||
|
e2a07ec452 | ||
|
ca2d425bc6 | ||
|
281c5631db | ||
|
58299f4d0d | ||
|
1ff4165b28 | ||
|
689670f4dc | ||
|
72dd5199bd | ||
|
be64536dcb | ||
|
f18a57d481 | ||
|
e72248c744 | ||
|
693752b77a | ||
|
a8ee8e875f | ||
|
48524b67cb | ||
|
39c5009247 | ||
|
018771bee3 | ||
|
13f227e843 | ||
|
d5f3643888 | ||
|
6fe6f4b196 | ||
|
b0d115431e | ||
|
97b2ba7daf | ||
|
c3f7a669a8 | ||
|
c9ce952af1 | ||
|
e7c5afefca | ||
|
f0e9c913a6 | ||
|
7b2e59a444 | ||
|
2035dab8be | ||
|
88af3a6fa5 | ||
|
ed1c3edfbd | ||
|
0e1e2b7d63 | ||
|
0c01df1128 | ||
|
179d2fe5bc | ||
|
3a932e7588 | ||
|
60cf81c963 | ||
|
9bf911515c | ||
|
fa50148d7b | ||
|
24d42506ec | ||
|
6a77802419 | ||
|
f4b09c3b6c | ||
|
40adf80fd2 | ||
|
3de8ea70d5 | ||
|
009f163c3d | ||
|
a7a6b99ac8 | ||
|
384d0ed35b | ||
|
4acd26880c | ||
|
c0041d10e7 | ||
|
f468fe9c02 | ||
|
fdadb3119d | ||
|
768d3ea445 | ||
|
4b1c205de4 | ||
|
cd145f993e | ||
|
99f75c28e6 | ||
|
aa0706284d | ||
|
55da8fcbda | ||
|
353596ec4f | ||
|
5cf275f1b5 |
8
.gitattributes
vendored
Normal file
8
.gitattributes
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/.gitattributes export-ignore
|
||||
/.gitignore export-ignore
|
||||
/.travis.yml export-ignore
|
||||
/examples export-ignore
|
||||
/phpunit.xml.dist export-ignore
|
||||
/tests export-ignore
|
||||
/Gemfile export-ignore
|
||||
/Gemfile.lock export-ignore
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
.idea
|
||||
_site
|
||||
Gemfile
|
||||
Gemfile.lock
|
||||
build
|
||||
vendor
|
||||
composer.lock
|
||||
phpunit.xml
|
24
.travis.yml
24
.travis.yml
@@ -1,7 +1,21 @@
|
||||
language: php
|
||||
php:
|
||||
- 5.3
|
||||
- 5.4
|
||||
- 5.5
|
||||
|
||||
script: phpunit --coverage-text ./
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
install:
|
||||
- composer update --prefer-source
|
||||
script:
|
||||
- vendor/bin/phpunit
|
||||
after_script:
|
||||
- vendor/bin/test-reporter
|
||||
env:
|
||||
global:
|
||||
secure: XnXSc7nxJMIrm/EJ1KuwlN4f+sj2R/sR0IFHdOdbOfMKyZ/u6WEgZ3vNrdeAsisiC/QUJJ00DGku1pAl3t3Hzvam0N/SiHtXjB1ZLVbX00S1PEZ6Z+h9zoaUBXWoN6+0OdKN0Xjmj2lwvTpvUxUZXNabilOw0F9WS/+JasofqBQ=
|
||||
sudo: false
|
||||
cache:
|
||||
directories:
|
||||
- vendor
|
||||
- $HOME/.composer/cache
|
||||
|
477
AltoRouter.php
477
AltoRouter.php
@@ -1,270 +1,297 @@
|
||||
<?php
|
||||
/*
|
||||
MIT License
|
||||
|
||||
class AltoRouter {
|
||||
Copyright (c) 2012 Danny van Kooten <hi@dannyvankooten.com>
|
||||
|
||||
protected $routes = array();
|
||||
protected $namedRoutes = array();
|
||||
protected $basePath = '';
|
||||
protected $matchTypes = array(
|
||||
'i' => '[0-9]++',
|
||||
'a' => '[0-9A-Za-z]++',
|
||||
'h' => '[0-9A-Fa-f]++',
|
||||
'*' => '.+?',
|
||||
'**' => '.++',
|
||||
'' => '[^/\.]++'
|
||||
);
|
||||
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:
|
||||
|
||||
/**
|
||||
* Create router in one call from config.
|
||||
*
|
||||
* @param array $routes
|
||||
* @param string $basePath
|
||||
* @param array $matchTypes
|
||||
*/
|
||||
public function __construct( $routes = array(), $basePath = '', $matchTypes = array() ) {
|
||||
$this->addRoutes($routes);
|
||||
$this->setBasePath($basePath);
|
||||
$this->addMatchTypes($matchTypes);
|
||||
}
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
/**
|
||||
* Add multiple routes at once from array in the following format:
|
||||
*
|
||||
* $routes = array(
|
||||
* array($method, $route, $target, $name)
|
||||
* );
|
||||
*
|
||||
* @param array $routes
|
||||
* @return void
|
||||
* @author Koen Punt
|
||||
*/
|
||||
public function addRoutes($routes){
|
||||
if(!is_array($routes) && !$routes instanceof Traversable) {
|
||||
throw new \Exception('Routes should be an array or an instance of Traversable');
|
||||
}
|
||||
foreach($routes as $route) {
|
||||
call_user_func_array(array($this, 'map'), $route);
|
||||
}
|
||||
}
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set the base path.
|
||||
* Useful if you are running your application from a subdirectory.
|
||||
*/
|
||||
public function setBasePath($basePath) {
|
||||
$this->basePath = $basePath;
|
||||
}
|
||||
class AltoRouter
|
||||
{
|
||||
|
||||
/**
|
||||
* Add named match types. It uses array_merge so keys can be overwritten.
|
||||
*
|
||||
* @param array $matchTypes The key is the name and the value is the regex.
|
||||
*/
|
||||
public function addMatchTypes($matchTypes) {
|
||||
$this->matchTypes = array_merge($this->matchTypes, $matchTypes);
|
||||
}
|
||||
/**
|
||||
* @var array Array of all routes (incl. named routes).
|
||||
*/
|
||||
protected $routes = [];
|
||||
|
||||
/**
|
||||
* Map a route to a target
|
||||
*
|
||||
* @param string $method One of 4 HTTP Methods, or a pipe-separated list of multiple HTTP Methods (GET|POST|PUT|DELETE)
|
||||
* @param string $route The route regex, custom regex must start with an @. You can use multiple pre-set regex filters, like [i:id]
|
||||
* @param mixed $target The target where this route should point to. Can be anything.
|
||||
* @param string $name Optional name of this route. Supply if you want to reverse route this url in your application.
|
||||
*/
|
||||
public function map($method, $route, $target, $name = null) {
|
||||
/**
|
||||
* @var array Array of all named routes.
|
||||
*/
|
||||
protected $namedRoutes = [];
|
||||
|
||||
$this->routes[] = array($method, $route, $target, $name);
|
||||
/**
|
||||
* @var string Can be used to ignore leading part of the Request URL (if main file lives in subdirectory of host)
|
||||
*/
|
||||
protected $basePath = '';
|
||||
|
||||
if($name) {
|
||||
if(isset($this->namedRoutes[$name])) {
|
||||
throw new \Exception("Can not redeclare route '{$name}'");
|
||||
} else {
|
||||
$this->namedRoutes[$name] = $route;
|
||||
}
|
||||
/**
|
||||
* @var array Array of default match types (regex helpers)
|
||||
*/
|
||||
protected $matchTypes = [
|
||||
'i' => '[0-9]++',
|
||||
'a' => '[0-9A-Za-z]++',
|
||||
'h' => '[0-9A-Fa-f]++',
|
||||
'*' => '.+?',
|
||||
'**' => '.++',
|
||||
'' => '[^/\.]++'
|
||||
];
|
||||
|
||||
}
|
||||
/**
|
||||
* Create router in one call from config.
|
||||
*
|
||||
* @param array $routes
|
||||
* @param string $basePath
|
||||
* @param array $matchTypes
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct(array $routes = [], $basePath = '', array $matchTypes = [])
|
||||
{
|
||||
$this->addRoutes($routes);
|
||||
$this->setBasePath($basePath);
|
||||
$this->addMatchTypes($matchTypes);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Retrieves all routes.
|
||||
* Useful if you want to process or display routes.
|
||||
* @return array All routes.
|
||||
*/
|
||||
public function getRoutes()
|
||||
{
|
||||
return $this->routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reversed routing
|
||||
*
|
||||
* Generate the URL for a named route. Replace regexes with supplied parameters
|
||||
*
|
||||
* @param string $routeName The name of the route.
|
||||
* @param array @params Associative array of parameters to replace placeholders with.
|
||||
* @return string The URL of the route with named parameters in place.
|
||||
*/
|
||||
public function generate($routeName, array $params = array()) {
|
||||
/**
|
||||
* Add multiple routes at once from array in the following format:
|
||||
*
|
||||
* $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 RuntimeException('Routes should be an array or an instance of Traversable');
|
||||
}
|
||||
foreach ($routes as $route) {
|
||||
call_user_func_array([$this, 'map'], $route);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if named route exists
|
||||
if(!isset($this->namedRoutes[$routeName])) {
|
||||
throw new \Exception("Route '{$routeName}' does not exist.");
|
||||
}
|
||||
/**
|
||||
* Set the base path.
|
||||
* Useful if you are running your application from a subdirectory.
|
||||
* @param string $basePath
|
||||
*/
|
||||
public function setBasePath($basePath)
|
||||
{
|
||||
$this->basePath = $basePath;
|
||||
}
|
||||
|
||||
// Replace named parameters
|
||||
$route = $this->namedRoutes[$routeName];
|
||||
/**
|
||||
* Add named match types. It uses array_merge so keys can be overwritten.
|
||||
*
|
||||
* @param array $matchTypes The key is the name and the value is the regex.
|
||||
*/
|
||||
public function addMatchTypes(array $matchTypes)
|
||||
{
|
||||
$this->matchTypes = array_merge($this->matchTypes, $matchTypes);
|
||||
}
|
||||
|
||||
// prepend base path to route url again
|
||||
$url = $this->basePath . $route;
|
||||
/**
|
||||
* Map a route to a target
|
||||
*
|
||||
* @param string $method One of 5 HTTP Methods, or a pipe-separated list of multiple HTTP Methods (GET|POST|PATCH|PUT|DELETE)
|
||||
* @param string $route The route regex, custom regex must start with an @. You can use multiple pre-set regex filters, like [i:id]
|
||||
* @param mixed $target The target where this route should point to. Can be anything.
|
||||
* @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)
|
||||
{
|
||||
|
||||
if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
|
||||
$this->routes[] = [$method, $route, $target, $name];
|
||||
|
||||
foreach($matches as $match) {
|
||||
list($block, $pre, $type, $param, $optional) = $match;
|
||||
if ($name) {
|
||||
if (isset($this->namedRoutes[$name])) {
|
||||
throw new RuntimeException("Can not redeclare route '{$name}'");
|
||||
}
|
||||
$this->namedRoutes[$name] = $route;
|
||||
}
|
||||
|
||||
if ($pre) {
|
||||
$block = substr($block, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(isset($params[$param])) {
|
||||
$url = str_replace($block, $params[$param], $url);
|
||||
} elseif ($optional) {
|
||||
$url = str_replace($pre . $block, '', $url);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Reversed routing
|
||||
*
|
||||
* Generate the URL for a named route. Replace regexes with supplied parameters
|
||||
*
|
||||
* @param string $routeName The name of the route.
|
||||
* @param array @params Associative array of parameters to replace placeholders with.
|
||||
* @return string The URL of the route with named parameters in place.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function generate($routeName, array $params = [])
|
||||
{
|
||||
|
||||
// Check if named route exists
|
||||
if (!isset($this->namedRoutes[$routeName])) {
|
||||
throw new RuntimeException("Route '{$routeName}' does not exist.");
|
||||
}
|
||||
|
||||
}
|
||||
// Replace named parameters
|
||||
$route = $this->namedRoutes[$routeName];
|
||||
|
||||
return $url;
|
||||
}
|
||||
// prepend base path to route url again
|
||||
$url = $this->basePath . $route;
|
||||
|
||||
/**
|
||||
* Match a given Request Url against stored routes
|
||||
* @param string $requestUrl
|
||||
* @param string $requestMethod
|
||||
* @return array|boolean Array with route information on success, false on failure (no match).
|
||||
*/
|
||||
public function match($requestUrl = null, $requestMethod = null) {
|
||||
if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $index => $match) {
|
||||
list($block, $pre, $type, $param, $optional) = $match;
|
||||
|
||||
$params = array();
|
||||
$match = false;
|
||||
if ($pre) {
|
||||
$block = substr($block, 1);
|
||||
}
|
||||
|
||||
// set Request Url if it isn't passed as parameter
|
||||
if($requestUrl === null) {
|
||||
$requestUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
|
||||
}
|
||||
if (isset($params[$param])) {
|
||||
// Part is found, replace for param value
|
||||
$url = str_replace($block, $params[$param], $url);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// strip base path from request url
|
||||
$requestUrl = substr($requestUrl, strlen($this->basePath));
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Strip query string (?a=b) from Request Url
|
||||
if (($strpos = strpos($requestUrl, '?')) !== false) {
|
||||
$requestUrl = substr($requestUrl, 0, $strpos);
|
||||
}
|
||||
/**
|
||||
* Match a given Request Url against stored routes
|
||||
* @param string $requestUrl
|
||||
* @param string $requestMethod
|
||||
* @return array|boolean Array with route information on success, false on failure (no match).
|
||||
*/
|
||||
public function match($requestUrl = null, $requestMethod = null)
|
||||
{
|
||||
|
||||
// set Request Method if it isn't passed as a parameter
|
||||
if($requestMethod === null) {
|
||||
$requestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
|
||||
}
|
||||
$params = [];
|
||||
|
||||
// Force request_order to be GP
|
||||
// http://www.mail-archive.com/internals@lists.php.net/msg33119.html
|
||||
$_REQUEST = array_merge($_GET, $_POST);
|
||||
// set Request Url if it isn't passed as parameter
|
||||
if ($requestUrl === null) {
|
||||
$requestUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
|
||||
}
|
||||
|
||||
foreach($this->routes as $handler) {
|
||||
list($method, $_route, $target, $name) = $handler;
|
||||
// strip base path from request url
|
||||
$requestUrl = substr($requestUrl, strlen($this->basePath));
|
||||
|
||||
$methods = explode('|', $method);
|
||||
$method_match = false;
|
||||
// Strip query string (?a=b) from Request Url
|
||||
if (($strpos = strpos($requestUrl, '?')) !== false) {
|
||||
$requestUrl = substr($requestUrl, 0, $strpos);
|
||||
}
|
||||
|
||||
// Check if request method matches. If not, abandon early. (CHEAP)
|
||||
foreach($methods as $method) {
|
||||
if (strcasecmp($requestMethod, $method) === 0) {
|
||||
$method_match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// set Request Method if it isn't passed as a parameter
|
||||
if ($requestMethod === null) {
|
||||
$requestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
|
||||
}
|
||||
|
||||
// Method did not match, continue to next route.
|
||||
if(!$method_match) continue;
|
||||
foreach ($this->routes as $handler) {
|
||||
list($methods, $route, $target, $name) = $handler;
|
||||
|
||||
// Check for a wildcard (matches all)
|
||||
if ($_route === '*') {
|
||||
$match = true;
|
||||
} elseif (isset($_route[0]) && $_route[0] === '@') {
|
||||
$match = preg_match('`' . substr($_route, 1) . '`u', $requestUrl, $params);
|
||||
} else {
|
||||
$route = null;
|
||||
$regex = false;
|
||||
$j = 0;
|
||||
$n = isset($_route[0]) ? $_route[0] : null;
|
||||
$i = 0;
|
||||
$method_match = (stripos($methods, $requestMethod) !== false);
|
||||
|
||||
// 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 === '{';
|
||||
}
|
||||
if (false === $regex && $c !== '/' && (!isset($requestUrl[$j]) || $c !== $requestUrl[$j])) {
|
||||
continue 2;
|
||||
}
|
||||
$j++;
|
||||
}
|
||||
$route .= $_route[$i++];
|
||||
}
|
||||
// Method did not match, continue to next route.
|
||||
if (!$method_match) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$regex = $this->compileRoute($route);
|
||||
$match = preg_match($regex, $requestUrl, $params);
|
||||
}
|
||||
if ($route === '*') {
|
||||
// * wildcard (matches all)
|
||||
$match = true;
|
||||
} 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 {
|
||||
// Compare longest non-param string with url
|
||||
if (strncmp($requestUrl, $route, $position) !== 0) {
|
||||
continue;
|
||||
}
|
||||
$regex = $this->compileRoute($route);
|
||||
$match = preg_match($regex, $requestUrl, $params) === 1;
|
||||
}
|
||||
|
||||
if(($match == true || $match > 0)) {
|
||||
if ($match) {
|
||||
if ($params) {
|
||||
foreach ($params as $key => $value) {
|
||||
if (is_numeric($key)) {
|
||||
unset($params[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($params) {
|
||||
foreach($params as $key => $value) {
|
||||
if(is_numeric($key)) unset($params[$key]);
|
||||
}
|
||||
}
|
||||
return [
|
||||
'target' => $target,
|
||||
'params' => $params,
|
||||
'name' => $name
|
||||
];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return array(
|
||||
'target' => $target,
|
||||
'params' => $params,
|
||||
'name' => $name
|
||||
);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Compile the regex for a given route (EXPENSIVE)
|
||||
* @param $route
|
||||
* @return string
|
||||
*/
|
||||
protected function compileRoute($route)
|
||||
{
|
||||
if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
|
||||
$matchTypes = $this->matchTypes;
|
||||
foreach ($matches as $match) {
|
||||
list($block, $pre, $type, $param, $optional) = $match;
|
||||
|
||||
/**
|
||||
* Compile the regex for a given route (EXPENSIVE)
|
||||
*/
|
||||
private function compileRoute($route) {
|
||||
if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
|
||||
if (isset($matchTypes[$type])) {
|
||||
$type = $matchTypes[$type];
|
||||
}
|
||||
if ($pre === '.') {
|
||||
$pre = '\.';
|
||||
}
|
||||
|
||||
$matchTypes = $this->matchTypes;
|
||||
foreach($matches as $match) {
|
||||
list($block, $pre, $type, $param, $optional) = $match;
|
||||
$optional = $optional !== '' ? '?' : null;
|
||||
|
||||
if (isset($matchTypes[$type])) {
|
||||
$type = $matchTypes[$type];
|
||||
}
|
||||
if ($pre === '.') {
|
||||
$pre = '\.';
|
||||
}
|
||||
//Older versions of PCRE require the 'P' in (?P<named>)
|
||||
$pattern = '(?:'
|
||||
. ($pre !== '' ? $pre : null)
|
||||
. '('
|
||||
. ($param !== '' ? "?P<$param>" : null)
|
||||
. $type
|
||||
. ')'
|
||||
. $optional
|
||||
. ')'
|
||||
. $optional;
|
||||
|
||||
//Older versions of PCRE require the 'P' in (?P<named>)
|
||||
$pattern = '(?:'
|
||||
. ($pre !== '' ? $pre : null)
|
||||
. '('
|
||||
. ($param !== '' ? "?P<$param>" : null)
|
||||
. $type
|
||||
. '))'
|
||||
. ($optional !== '' ? '?' : null);
|
||||
|
||||
$route = str_replace($block, $pattern, $route);
|
||||
}
|
||||
|
||||
}
|
||||
return "`^$route$`u";
|
||||
}
|
||||
$route = str_replace($block, $pattern, $route);
|
||||
}
|
||||
}
|
||||
return "`^$route$`u";
|
||||
}
|
||||
}
|
||||
|
@@ -1,423 +0,0 @@
|
||||
<?php
|
||||
|
||||
require 'AltoRouter.php';
|
||||
|
||||
class AltoRouterDebug extends AltoRouter{
|
||||
|
||||
public function getNamedRoutes(){
|
||||
return $this->namedRoutes;
|
||||
}
|
||||
|
||||
public function getRoutes(){
|
||||
return $this->routes;
|
||||
}
|
||||
|
||||
public function getBasePath(){
|
||||
return $this->basePath;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SimpleTraversable implements Iterator{
|
||||
|
||||
protected $_position = 0;
|
||||
|
||||
protected $_data = array(
|
||||
array('GET', '/foo', 'foo_action', null),
|
||||
array('POST', '/bar', 'bar_action', 'second_route')
|
||||
);
|
||||
|
||||
public function current(){
|
||||
return $this->_data[$this->_position];
|
||||
}
|
||||
public function key(){
|
||||
return $this->_position;
|
||||
}
|
||||
public function next(){
|
||||
++$this->_position;
|
||||
}
|
||||
public function rewind(){
|
||||
$this->_position = 0;
|
||||
}
|
||||
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
|
||||
{
|
||||
/**
|
||||
* @var AltoRouter
|
||||
*/
|
||||
protected $router;
|
||||
|
||||
/**
|
||||
* Sets up the fixture, for example, opens a network connection.
|
||||
* This method is called before a test is executed.
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
$this->router = new AltoRouterDebug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tears down the fixture, for example, closes a network connection.
|
||||
* This method is called after a test is executed.
|
||||
*/
|
||||
protected function tearDown()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::addRoutes
|
||||
*/
|
||||
public function testAddRoutes()
|
||||
{
|
||||
$method = 'POST';
|
||||
$route = '/[:controller]/[:action]';
|
||||
$target = function(){};
|
||||
|
||||
$this->router->addRoutes(array(
|
||||
array($method, $route, $target),
|
||||
array($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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::addRoutes
|
||||
*/
|
||||
public function testAddRoutesAcceptsTraverable()
|
||||
{
|
||||
$traversable = new SimpleTraversable();
|
||||
$this->router->addRoutes($traversable);
|
||||
|
||||
$traversable->rewind();
|
||||
|
||||
$first = $traversable->current();
|
||||
$traversable->next();
|
||||
$second = $traversable->current();
|
||||
|
||||
$routes = $this->router->getRoutes();
|
||||
|
||||
$this->assertEquals($first, $routes[0]);
|
||||
$this->assertEquals($second, $routes[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::addRoutes
|
||||
* @expectedException Exception
|
||||
*/
|
||||
public function testAddRoutesThrowsExceptionOnInvalidArgument()
|
||||
{
|
||||
$this->router->addRoutes(new stdClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::setBasePath
|
||||
*/
|
||||
public function testSetBasePath()
|
||||
{
|
||||
$basePath = $this->router->setBasePath('/some/path');
|
||||
$this->assertEquals('/some/path', $this->router->getBasePath());
|
||||
|
||||
$basePath = $this->router->setBasePath('/some/path');
|
||||
$this->assertEquals('/some/path', $this->router->getBasePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::map
|
||||
*/
|
||||
public function testMap()
|
||||
{
|
||||
$method = 'POST';
|
||||
$route = '/[:controller]/[:action]';
|
||||
$target = function(){};
|
||||
|
||||
$this->router->map($method, $route, $target);
|
||||
|
||||
$routes = $this->router->getRoutes();
|
||||
|
||||
$this->assertEquals(array($method, $route, $target, null), $routes[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::map
|
||||
*/
|
||||
public function testMapWithName()
|
||||
{
|
||||
$method = 'POST';
|
||||
$route = '/[:controller]/[:action]';
|
||||
$target = function(){};
|
||||
$name = 'myroute';
|
||||
|
||||
$this->router->map($method, $route, $target, $name);
|
||||
|
||||
$routes = $this->router->getRoutes();
|
||||
$this->assertEquals(array($method, $route, $target, $name), $routes[0]);
|
||||
|
||||
$named_routes = $this->router->getNamedRoutes();
|
||||
$this->assertEquals($route, $named_routes[$name]);
|
||||
|
||||
try{
|
||||
$this->router->map($method, $route, $target, $name);
|
||||
$this->fail('Should not be able to add existing named route');
|
||||
}catch(Exception $e){
|
||||
$this->assertEquals("Can not redeclare route '{$name}'", $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::generate
|
||||
*/
|
||||
public function testGenerate()
|
||||
{
|
||||
$params = array(
|
||||
'controller' => 'test',
|
||||
'action' => 'someaction'
|
||||
);
|
||||
|
||||
$this->router->map('GET', '/[:controller]/[:action]', function(){}, 'foo_route');
|
||||
|
||||
$this->assertEquals('/test/someaction',
|
||||
$this->router->generate('foo_route', $params));
|
||||
|
||||
$params = array(
|
||||
'controller' => 'test',
|
||||
'action' => 'someaction',
|
||||
'type' => 'json'
|
||||
);
|
||||
|
||||
$this->assertEquals('/test/someaction',
|
||||
$this->router->generate('foo_route', $params));
|
||||
|
||||
}
|
||||
|
||||
public function testGenerateWithOptionalUrlParts()
|
||||
{
|
||||
$this->router->map('GET', '/[:controller]/[:action].[:type]?', function(){}, 'bar_route');
|
||||
|
||||
$params = array(
|
||||
'controller' => 'test',
|
||||
'action' => 'someaction'
|
||||
);
|
||||
|
||||
$this->assertEquals('/test/someaction',
|
||||
$this->router->generate('bar_route', $params));
|
||||
|
||||
$params = array(
|
||||
'controller' => 'test',
|
||||
'action' => 'someaction',
|
||||
'type' => 'json'
|
||||
);
|
||||
|
||||
$this->assertEquals('/test/someaction.json',
|
||||
$this->router->generate('bar_route', $params));
|
||||
}
|
||||
|
||||
public function testGenerateWithNonexistingRoute()
|
||||
{
|
||||
try{
|
||||
$this->router->generate('nonexisting_route');
|
||||
$this->fail('Should trigger an exception on nonexisting named route');
|
||||
}catch(Exception $e){
|
||||
$this->assertEquals("Route 'nonexisting_route' does not exist.", $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::match
|
||||
* @covers AltoRouter::compileRoute
|
||||
*/
|
||||
public function testMatch()
|
||||
{
|
||||
$this->router->map('GET', '/foo/[:controller]/[:action]', 'foo_action', 'foo_route');
|
||||
|
||||
$this->assertEquals(array(
|
||||
'target' => 'foo_action',
|
||||
'params' => array(
|
||||
'controller' => 'test',
|
||||
'action' => 'do'
|
||||
),
|
||||
'name' => 'foo_route'
|
||||
), $this->router->match('/foo/test/do', 'GET'));
|
||||
|
||||
$this->assertFalse($this->router->match('/foo/test/do', 'POST'));
|
||||
|
||||
$this->assertEquals(array(
|
||||
'target' => 'foo_action',
|
||||
'params' => array(
|
||||
'controller' => 'test',
|
||||
'action' => 'do'
|
||||
),
|
||||
'name' => 'foo_route'
|
||||
), $this->router->match('/foo/test/do?param=value', 'GET'));
|
||||
|
||||
}
|
||||
|
||||
public function testMatchWithFixedParamValues()
|
||||
{
|
||||
$this->router->map('POST','/users/[i:id]/[delete|update:action]', 'usersController#doAction', 'users_do');
|
||||
|
||||
$this->assertEquals(array(
|
||||
'target' => 'usersController#doAction',
|
||||
'params' => array(
|
||||
'id' => 1,
|
||||
'action' => 'delete'
|
||||
),
|
||||
'name' => 'users_do'
|
||||
), $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'));
|
||||
}
|
||||
|
||||
public function testMatchWithServerVars()
|
||||
{
|
||||
$this->router->map('GET', '/foo/[:controller]/[:action]', 'foo_action', 'foo_route');
|
||||
|
||||
$_SERVER['REQUEST_URI'] = '/foo/test/do';
|
||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||
|
||||
$this->assertEquals(array(
|
||||
'target' => 'foo_action',
|
||||
'params' => array(
|
||||
'controller' => 'test',
|
||||
'action' => 'do'
|
||||
),
|
||||
'name' => 'foo_route'
|
||||
), $this->router->match());
|
||||
}
|
||||
|
||||
public function testMatchWithOptionalUrlParts()
|
||||
{
|
||||
$this->router->map('GET', '/bar/[:controller]/[:action].[:type]?', 'bar_action', 'bar_route');
|
||||
|
||||
$this->assertEquals(array(
|
||||
'target' => 'bar_action',
|
||||
'params' => array(
|
||||
'controller' => 'test',
|
||||
'action' => 'do',
|
||||
'type' => 'json'
|
||||
),
|
||||
'name' => 'bar_route'
|
||||
), $this->router->match('/bar/test/do.json', 'GET'));
|
||||
|
||||
}
|
||||
|
||||
public function testMatchWithWildcard()
|
||||
{
|
||||
$this->router->map('GET', '/a', 'foo_action', 'foo_route');
|
||||
$this->router->map('GET', '*', 'bar_action', 'bar_route');
|
||||
|
||||
$this->assertEquals(array(
|
||||
'target' => 'bar_action',
|
||||
'params' => array(),
|
||||
'name' => 'bar_route'
|
||||
), $this->router->match('/everything', 'GET'));
|
||||
|
||||
}
|
||||
|
||||
public function testMatchWithCustomRegexp()
|
||||
{
|
||||
$this->router->map('GET', '@^/[a-z]*$', 'bar_action', 'bar_route');
|
||||
|
||||
$this->assertEquals(array(
|
||||
'target' => 'bar_action',
|
||||
'params' => array(),
|
||||
'name' => 'bar_route'
|
||||
), $this->router->match('/everything', 'GET'));
|
||||
|
||||
$this->assertFalse($this->router->match('/some-other-thing', 'GET'));
|
||||
|
||||
}
|
||||
|
||||
public function testMatchWithUnicodeRegex()
|
||||
{
|
||||
$pattern = '/(?<path>[^';
|
||||
// Arabic characters
|
||||
$pattern .= '\x{0600}-\x{06FF}';
|
||||
$pattern .= '\x{FB50}-\x{FDFD}';
|
||||
$pattern .= '\x{FE70}-\x{FEFF}';
|
||||
$pattern .= '\x{0750}-\x{077F}';
|
||||
// Alphanumeric, /, _, - and space characters
|
||||
$pattern .= 'a-zA-Z0-9\/_-\s';
|
||||
// 'ZERO WIDTH NON-JOINER'
|
||||
$pattern .= '\x{200C}';
|
||||
$pattern .= ']+)';
|
||||
|
||||
$this->router->map('GET', '@' . $pattern, 'unicode_action', 'unicode_route');
|
||||
|
||||
$this->assertEquals(array(
|
||||
'target' => 'unicode_action',
|
||||
'name' => 'unicode_route',
|
||||
'params' => array(
|
||||
'path' => '大家好'
|
||||
)
|
||||
), $this->router->match('/大家好', 'GET'));
|
||||
|
||||
$this->assertFalse($this->router->match('/﷽', 'GET'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::addMatchTypes
|
||||
*/
|
||||
public function testMatchWithCustomNamedRegex()
|
||||
{
|
||||
$this->router->addMatchTypes(array('cId' => '[a-zA-Z]{2}[0-9](?:_[0-9]++)?'));
|
||||
$this->router->map('GET', '/bar/[cId:customId]', 'bar_action', 'bar_route');
|
||||
|
||||
$this->assertEquals(array(
|
||||
'target' => 'bar_action',
|
||||
'params' => array(
|
||||
'customId' => 'AB1',
|
||||
),
|
||||
'name' => 'bar_route'
|
||||
), $this->router->match('/bar/AB1', 'GET'));
|
||||
|
||||
$this->assertEquals(array(
|
||||
'target' => 'bar_action',
|
||||
'params' => array(
|
||||
'customId' => 'AB1_0123456789',
|
||||
),
|
||||
'name' => 'bar_route'
|
||||
), $this->router->match('/bar/AB1_0123456789', 'GET'));
|
||||
|
||||
$this->assertFalse($this->router->match('/some-other-thing', 'GET'));
|
||||
|
||||
}
|
||||
|
||||
public function testMatchWithCustomNamedUnicodeRegex()
|
||||
{
|
||||
$pattern = '[^';
|
||||
// Arabic characters
|
||||
$pattern .= '\x{0600}-\x{06FF}';
|
||||
$pattern .= '\x{FB50}-\x{FDFD}';
|
||||
$pattern .= '\x{FE70}-\x{FEFF}';
|
||||
$pattern .= '\x{0750}-\x{077F}';
|
||||
$pattern .= ']+';
|
||||
|
||||
$this->router->addMatchTypes(array('nonArabic' => $pattern));
|
||||
$this->router->map('GET', '/bar/[nonArabic:string]', 'non_arabic_action', 'non_arabic_route');
|
||||
|
||||
$this->assertEquals(array(
|
||||
'target' => 'non_arabic_action',
|
||||
'name' => 'non_arabic_route',
|
||||
'params' => array(
|
||||
'string' => 'some-path'
|
||||
)
|
||||
), $this->router->match('/bar/some-path', 'GET'));
|
||||
|
||||
$this->assertFalse($this->router->match('/﷽', 'GET'));
|
||||
}
|
||||
}
|
9
LICENSE.md
Normal file
9
LICENSE.md
Normal 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.
|
100
README.md
100
README.md
@@ -1,77 +1,41 @@
|
||||
# AltoRouter [](http://travis-ci.org/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 [](http://travis-ci.org/dannyvankooten/AltoRouter) [](https://packagist.org/packages/altorouter/altorouter) [](https://packagist.org/packages/altorouter/altorouter) [](https://codeclimate.com/github/dannyvankooten/AltoRouter) [](https://codeclimate.com/github/dannyvankooten/AltoRouter)
|
||||
AltoRouter is a small but powerful routing class, heavily inspired by [klein.php](https://github.com/chriso/klein.php/).
|
||||
|
||||
* Dynamic routing with named parameters
|
||||
```php
|
||||
$router = new AltoRouter();
|
||||
|
||||
// map homepage
|
||||
$router->map('GET', '/', function() {
|
||||
require __DIR__ . '/views/home.php';
|
||||
});
|
||||
|
||||
// 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
|
||||
|
||||
* Can be used with all HTTP Methods
|
||||
* Dynamic routing with named route parameters
|
||||
* Reversed routing
|
||||
* Flexible regular expression routing (inspired by [Sinatra](http://www.sinatrarb.com/))
|
||||
* Custom regexes
|
||||
|
||||
## Getting started
|
||||
|
||||
1. PHP 5.3.x is required
|
||||
2. Install AltoRouter using Composer or manually
|
||||
2. Setup URL rewriting so that all requests are handled by **index.php**
|
||||
3. Create an instance of AltoRouter, map your routes and match a request.
|
||||
4. Have a look at the basic example in the `examples` directory for a better understanding on how to use AltoRouter.
|
||||
|
||||
## Routing
|
||||
```php
|
||||
$router = new AltoRouter();
|
||||
$router->setBasePath('/AltoRouter'); // (optional) the subdir AltoRouter lives in
|
||||
|
||||
// mapping routes
|
||||
$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');
|
||||
|
||||
// reversed routing
|
||||
$router->generate('users_show', array('id' => 5));
|
||||
|
||||
```
|
||||
|
||||
**You can use the following limits on your named parameters. AltoRouter will create the correct regexes for you.**
|
||||
|
||||
```php
|
||||
* // Match all request URIs
|
||||
[i] // Match an integer
|
||||
[i:id] // Match an integer as 'id'
|
||||
[a:action] // Match alphanumeric characters as 'action'
|
||||
[h:key] // Match hexadecimal characters as 'key'
|
||||
[:action] // Match anything up to the next / or end of the URI as 'action'
|
||||
[create|edit:action] // Match either 'create' or 'edit' as 'action'
|
||||
[*] // Catch all (lazy, stops at the next trailing slash)
|
||||
[*:trailing] // Catch all as 'trailing' (lazy)
|
||||
[**:trailing] // Catch all (possessive - will match the rest of the URI)
|
||||
.[:format]? // Match an optional parameter 'format' - a / or . before the block is also optional
|
||||
```
|
||||
|
||||
**Some more complicated examples**
|
||||
|
||||
```php
|
||||
@/(?[A-Za-z]{2}_[A-Za-z]{2})$ // custom regex, matches language codes like "en_us" etc.
|
||||
/posts/[*:title][i:id] // Matches "/posts/this-is-a-title-123"
|
||||
/output.[xml|json:format]? // Matches "/output", "output.xml", "output.json"
|
||||
/[:controller]?/[:action]? // Matches the typical /controller/action format
|
||||
```
|
||||
|
||||
**The character before the colon (the 'match type') is a shortcut for one of the following regular expressions**
|
||||
|
||||
```php
|
||||
'i' => '[0-9]++'
|
||||
'a' => '[0-9A-Za-z]++'
|
||||
'h' => '[0-9A-Fa-f]++'
|
||||
'*' => '.+?'
|
||||
'**' => '.++'
|
||||
'' => '[^/\.]++'
|
||||
```
|
||||
|
||||
**New match types can be added using the `addMatchTypes()` method**
|
||||
|
||||
```php
|
||||
$router->addMatchTypes(array('cId' => '[a-zA-Z]{2}[0-9](?:_[0-9]++)?'));
|
||||
```
|
||||
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)
|
||||
- [Map your routes](http://altorouter.com/usage/mapping-routes.html)
|
||||
- [Match requests](http://altorouter.com/usage/matching-requests.html)
|
||||
- [Process the request your preferred way](http://altorouter.com/usage/processing-requests.html)
|
||||
|
||||
## Contributors
|
||||
- [Danny van Kooten](https://github.com/dannyvankooten)
|
||||
@@ -81,9 +45,9 @@ $router->addMatchTypes(array('cId' => '[a-zA-Z]{2}[0-9](?:_[0-9]++)?'));
|
||||
|
||||
## License
|
||||
|
||||
(MIT License)
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2012-2013 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:
|
||||
|
||||
|
@@ -20,9 +20,17 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
"php": ">=5.6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "5.7.*",
|
||||
"codeclimate/php-test-reporter": "dev-master",
|
||||
"squizlabs/php_codesniffer": "3.4.2"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["AltoRouter.php"]
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vendor/bin/phpunit"
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
@@ -16,12 +24,12 @@ $match = $router->match();
|
||||
|
||||
<h3>Current request: </h3>
|
||||
<pre>
|
||||
Target: <?php var_dump($match['target']); ?>
|
||||
Params: <?php var_dump($match['params']); ?>
|
||||
Name: <?php var_dump($match['name']); ?>
|
||||
Target: <?php var_dump($match['target']); ?>
|
||||
Params: <?php var_dump($match['params']); ?>
|
||||
Name: <?php var_dump($match['name']); ?>
|
||||
</pre>
|
||||
|
||||
<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
10
phpcs.xml
Normal 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>
|
17
phpunit.xml.dist
Normal file
17
phpunit.xml.dist
Normal file
@@ -0,0 +1,17 @@
|
||||
<phpunit
|
||||
colors="true"
|
||||
verbose="true">
|
||||
<testsuites>
|
||||
<testsuite name="altorouter">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<file>./AltoRouter.php</file>
|
||||
</whitelist>
|
||||
</filter>
|
||||
<logging>
|
||||
<log type="coverage-clover" target="build/logs/clover.xml"/>
|
||||
</logging>
|
||||
</phpunit>
|
574
tests/AltoRouterTest.php
Normal file
574
tests/AltoRouterTest.php
Normal file
@@ -0,0 +1,574 @@
|
||||
<?php
|
||||
|
||||
require 'AltoRouter.php';
|
||||
|
||||
class AltoRouterDebug extends AltoRouter
|
||||
{
|
||||
public function getNamedRoutes()
|
||||
{
|
||||
return $this->namedRoutes;
|
||||
}
|
||||
|
||||
public function getBasePath()
|
||||
{
|
||||
return $this->basePath;
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleTraversable implements Iterator
|
||||
{
|
||||
|
||||
protected $_position = 0;
|
||||
|
||||
protected $_data = [
|
||||
['GET', '/foo', 'foo_action', null],
|
||||
['POST', '/bar', 'bar_action', 'second_route']
|
||||
];
|
||||
|
||||
public function current()
|
||||
{
|
||||
return $this->_data[$this->_position];
|
||||
}
|
||||
public function key()
|
||||
{
|
||||
return $this->_position;
|
||||
}
|
||||
public function next()
|
||||
{
|
||||
++$this->_position;
|
||||
}
|
||||
public function rewind()
|
||||
{
|
||||
$this->_position = 0;
|
||||
}
|
||||
public function valid()
|
||||
{
|
||||
return isset($this->_data[$this->_position]);
|
||||
}
|
||||
}
|
||||
|
||||
class AltoRouterTest extends PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var AltoRouter
|
||||
*/
|
||||
protected $router;
|
||||
|
||||
/**
|
||||
* Sets up the fixture, for example, opens a network connection.
|
||||
* This method is called before a test is executed.
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
$this->router = new AltoRouterDebug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tears down the fixture, for example, closes a network connection.
|
||||
* This method is called after a test is executed.
|
||||
*/
|
||||
protected function tearDown()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::getRoutes
|
||||
*/
|
||||
public function testGetRoutes()
|
||||
{
|
||||
$method = 'POST';
|
||||
$route = '/[:controller]/[:action]';
|
||||
$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([[$method, $route, $target, null]], $this->router->getRoutes());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::addRoutes
|
||||
*/
|
||||
public function testAddRoutes()
|
||||
{
|
||||
$method = 'POST';
|
||||
$route = '/[:controller]/[:action]';
|
||||
$target = static function () {
|
||||
};
|
||||
|
||||
$this->router->addRoutes([
|
||||
[$method, $route, $target],
|
||||
[$method, $route, $target, 'second_route']
|
||||
]);
|
||||
|
||||
$routes = $this->router->getRoutes();
|
||||
|
||||
$this->assertEquals([$method, $route, $target, null], $routes[0]);
|
||||
$this->assertEquals([$method, $route, $target, 'second_route'], $routes[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::addRoutes
|
||||
*/
|
||||
public function testAddRoutesAcceptsTraverable()
|
||||
{
|
||||
$traversable = new SimpleTraversable();
|
||||
$this->router->addRoutes($traversable);
|
||||
|
||||
$traversable->rewind();
|
||||
|
||||
$first = $traversable->current();
|
||||
$traversable->next();
|
||||
$second = $traversable->current();
|
||||
|
||||
$routes = $this->router->getRoutes();
|
||||
|
||||
$this->assertEquals($first, $routes[0]);
|
||||
$this->assertEquals($second, $routes[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::addRoutes
|
||||
* @expectedException Exception
|
||||
*/
|
||||
public function testAddRoutesThrowsExceptionOnInvalidArgument()
|
||||
{
|
||||
$this->router->addRoutes(new stdClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::setBasePath
|
||||
*/
|
||||
public function testSetBasePath()
|
||||
{
|
||||
$basePath = $this->router->setBasePath('/some/path');
|
||||
$this->assertEquals('/some/path', $this->router->getBasePath());
|
||||
|
||||
$basePath = $this->router->setBasePath('/some/path');
|
||||
$this->assertEquals('/some/path', $this->router->getBasePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::map
|
||||
*/
|
||||
public function testMap()
|
||||
{
|
||||
$method = 'POST';
|
||||
$route = '/[:controller]/[:action]';
|
||||
$target = static function () {
|
||||
};
|
||||
|
||||
$this->router->map($method, $route, $target);
|
||||
|
||||
$routes = $this->router->getRoutes();
|
||||
|
||||
$this->assertEquals([$method, $route, $target, null], $routes[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::map
|
||||
*/
|
||||
public function testMapWithName()
|
||||
{
|
||||
$method = 'POST';
|
||||
$route = '/[:controller]/[:action]';
|
||||
$target = static function () {
|
||||
};
|
||||
$name = 'myroute';
|
||||
|
||||
$this->router->map($method, $route, $target, $name);
|
||||
|
||||
$routes = $this->router->getRoutes();
|
||||
$this->assertEquals([$method, $route, $target, $name], $routes[0]);
|
||||
|
||||
$named_routes = $this->router->getNamedRoutes();
|
||||
$this->assertEquals($route, $named_routes[$name]);
|
||||
|
||||
try {
|
||||
$this->router->map($method, $route, $target, $name);
|
||||
$this->fail('Should not be able to add existing named route');
|
||||
} catch (Exception $e) {
|
||||
$this->assertEquals("Can not redeclare route '{$name}'", $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::generate
|
||||
*/
|
||||
public function testGenerate()
|
||||
{
|
||||
$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)
|
||||
);
|
||||
|
||||
$params = [
|
||||
'controller' => 'test',
|
||||
'action' => 'someaction',
|
||||
'type' => 'json'
|
||||
];
|
||||
|
||||
$this->assertEquals(
|
||||
'/test/someaction',
|
||||
$this->router->generate('foo_route', $params)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::generate
|
||||
*/
|
||||
public function testGenerateWithOptionalUrlParts()
|
||||
{
|
||||
$this->router->map('GET', '/[:controller]/[:action].[:type]?', static function () {
|
||||
}, 'bar_route');
|
||||
|
||||
$params = [
|
||||
'controller' => 'test',
|
||||
'action' => 'someaction'
|
||||
];
|
||||
|
||||
$this->assertEquals(
|
||||
'/test/someaction',
|
||||
$this->router->generate('bar_route', $params)
|
||||
);
|
||||
|
||||
$params = [
|
||||
'controller' => 'test',
|
||||
'action' => 'someaction',
|
||||
'type' => 'json'
|
||||
];
|
||||
|
||||
$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 {
|
||||
$this->router->generate('nonexisting_route');
|
||||
$this->fail('Should trigger an exception on nonexisting named route');
|
||||
} catch (Exception $e) {
|
||||
$this->assertEquals("Route 'nonexisting_route' does not exist.", $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::match
|
||||
* @covers AltoRouter::compileRoute
|
||||
*/
|
||||
public function testMatch()
|
||||
{
|
||||
$this->router->map('GET', '/foo/[:controller]/[:action]', 'foo_action', 'foo_route');
|
||||
|
||||
$this->assertEquals([
|
||||
'target' => 'foo_action',
|
||||
'params' => [
|
||||
'controller' => 'test',
|
||||
'action' => 'do'
|
||||
],
|
||||
'name' => 'foo_route'
|
||||
], $this->router->match('/foo/test/do', 'GET'));
|
||||
|
||||
$this->assertFalse($this->router->match('/foo/test/do', 'POST'));
|
||||
|
||||
$this->assertEquals([
|
||||
'target' => 'foo_action',
|
||||
'params' => [
|
||||
'controller' => 'test',
|
||||
'action' => 'do'
|
||||
],
|
||||
'name' => 'foo_route'
|
||||
], $this->router->match('/foo/test/do?param=value', 'GET'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::match
|
||||
*/
|
||||
public function testMatchWithNonRegex()
|
||||
{
|
||||
$this->router->map('GET', '/about-us', 'PagesController#about', 'about_us');
|
||||
|
||||
$this->assertEquals([
|
||||
'target' => 'PagesController#about',
|
||||
'params' => [],
|
||||
'name' => 'about_us'
|
||||
], $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->assertEquals([
|
||||
'target' => 'usersController#doAction',
|
||||
'params' => [
|
||||
'id' => 1,
|
||||
'action' => 'delete'
|
||||
],
|
||||
'name' => 'users_do'
|
||||
], $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');
|
||||
|
||||
$_SERVER['REQUEST_URI'] = '/foo/test/do';
|
||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||
|
||||
$this->assertEquals([
|
||||
'target' => 'foo_action',
|
||||
'params' => [
|
||||
'controller' => 'test',
|
||||
'action' => 'do'
|
||||
],
|
||||
'name' => 'foo_route'
|
||||
], $this->router->match());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::match
|
||||
*/
|
||||
public function testMatchWithOptionalUrlParts()
|
||||
{
|
||||
$this->router->map('GET', '/bar/[:controller]/[:action].[:type]?', 'bar_action', 'bar_route');
|
||||
|
||||
$this->assertEquals([
|
||||
'target' => 'bar_action',
|
||||
'params' => [
|
||||
'controller' => 'test',
|
||||
'action' => 'do',
|
||||
'type' => 'json'
|
||||
],
|
||||
'name' => 'bar_route'
|
||||
], $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([
|
||||
'target' => 'bar_action',
|
||||
'params' => [],
|
||||
'name' => 'bar_route'
|
||||
], $this->router->match('/everything', 'GET'));
|
||||
}
|
||||
/**
|
||||
* @covers AltoRouter::match
|
||||
*/
|
||||
public function testMatchWithCustomRegexp()
|
||||
{
|
||||
$this->router->map('GET', '@^/[a-z]*$', 'bar_action', 'bar_route');
|
||||
|
||||
$this->assertEquals([
|
||||
'target' => 'bar_action',
|
||||
'params' => [],
|
||||
'name' => 'bar_route'
|
||||
], $this->router->match('/everything', 'GET'));
|
||||
|
||||
$this->assertFalse($this->router->match('/some-other-thing', 'GET'));
|
||||
}
|
||||
/**
|
||||
* @covers AltoRouter::match
|
||||
*/
|
||||
public function testMatchWithUnicodeRegex()
|
||||
{
|
||||
$pattern = '/(?<path>[^';
|
||||
// Arabic characters
|
||||
$pattern .= '\x{0600}-\x{06FF}';
|
||||
$pattern .= '\x{FB50}-\x{FDFD}';
|
||||
$pattern .= '\x{FE70}-\x{FEFF}';
|
||||
$pattern .= '\x{0750}-\x{077F}';
|
||||
// Alphanumeric, /, _, - and space characters
|
||||
$pattern .= 'a-zA-Z0-9\/_\-\s';
|
||||
// 'ZERO WIDTH NON-JOINER'
|
||||
$pattern .= '\x{200C}';
|
||||
$pattern .= ']+)';
|
||||
|
||||
$this->router->map('GET', '@' . $pattern, 'unicode_action', 'unicode_route');
|
||||
|
||||
$this->assertEquals([
|
||||
'target' => 'unicode_action',
|
||||
'name' => 'unicode_route',
|
||||
'params' => [
|
||||
'path' => '大家好'
|
||||
]
|
||||
], $this->router->match('/大家好', 'GET'));
|
||||
|
||||
$this->assertFalse($this->router->match('/﷽', 'GET'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AltoRouter::addMatchTypes
|
||||
*/
|
||||
public function testMatchWithCustomNamedRegex()
|
||||
{
|
||||
$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([
|
||||
'target' => 'bar_action',
|
||||
'params' => [
|
||||
'customId' => 'AB1',
|
||||
],
|
||||
'name' => 'bar_route'
|
||||
], $this->router->match('/bar/AB1', 'GET'));
|
||||
|
||||
$this->assertEquals([
|
||||
'target' => 'bar_action',
|
||||
'params' => [
|
||||
'customId' => 'AB1_0123456789',
|
||||
],
|
||||
'name' => 'bar_route'
|
||||
], $this->router->match('/bar/AB1_0123456789', 'GET'));
|
||||
|
||||
$this->assertFalse($this->router->match('/some-other-thing', 'GET'));
|
||||
}
|
||||
/**
|
||||
* @covers AltoRouter::addMatchTypes
|
||||
*/
|
||||
public function testMatchWithCustomNamedUnicodeRegex()
|
||||
{
|
||||
$pattern = '[^';
|
||||
// Arabic characters
|
||||
$pattern .= '\x{0600}-\x{06FF}';
|
||||
$pattern .= '\x{FB50}-\x{FDFD}';
|
||||
$pattern .= '\x{FE70}-\x{FEFF}';
|
||||
$pattern .= '\x{0750}-\x{077F}';
|
||||
$pattern .= ']+';
|
||||
|
||||
$this->router->addMatchTypes(['nonArabic' => $pattern]);
|
||||
$this->router->map('GET', '/bar/[nonArabic:string]', 'non_arabic_action', 'non_arabic_route');
|
||||
|
||||
$this->assertEquals([
|
||||
'target' => 'non_arabic_action',
|
||||
'name' => 'non_arabic_route',
|
||||
'params' => [
|
||||
'string' => 'some-path'
|
||||
]
|
||||
], $this->router->match('/bar/some-path', 'GET'));
|
||||
|
||||
$this->assertFalse($this->router->match('/﷽', 'GET'));
|
||||
}
|
||||
}
|
92
tests/benchmark.php
Normal file
92
tests/benchmark.php
Normal 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;
|
Reference in New Issue
Block a user