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

add type hinting, removed array long syntax

This commit is contained in:
Félix Dorn
2019-07-03 21:40:17 +02:00
parent 24360d6162
commit 2360f527b0

View File

@@ -11,277 +11,287 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
class AltoRouter { class AltoRouter
{
/** /**
* @var array Array of all routes (incl. named routes). * @var array Array of all routes (incl. named routes).
*/ */
protected $routes = array(); protected $routes = [];
/** /**
* @var array Array of all named routes. * @var array Array of all named routes.
*/ */
protected $namedRoutes = array(); protected $namedRoutes = [];
/** /**
* @var string Can be used to ignore leading part of the Request URL (if main file lives in subdirectory of host) * @var string Can be used to ignore leading part of the Request URL (if main file lives in subdirectory of host)
*/ */
protected $basePath = ''; protected $basePath = '';
/** /**
* @var array Array of default match types (regex helpers) * @var array Array of default match types (regex helpers)
*/ */
protected $matchTypes = array( protected $matchTypes = [
'i' => '[0-9]++', 'i' => '[0-9]++',
'a' => '[0-9A-Za-z]++', 'a' => '[0-9A-Za-z]++',
'h' => '[0-9A-Fa-f]++', 'h' => '[0-9A-Fa-f]++',
'*' => '.+?', '*' => '.+?',
'**' => '.++', '**' => '.++',
'' => '[^/\.]++' '' => '[^/\.]++'
); ];
/** /**
* Create router in one call from config. * Create router in one call from config.
* *
* @param array $routes * @param array $routes
* @param string $basePath * @param string $basePath
* @param array $matchTypes * @param array $matchTypes
*/ * @throws Exception
public function __construct( $routes = array(), $basePath = '', $matchTypes = array() ) { */
$this->addRoutes($routes); public function __construct(array $routes = [], $basePath = '', $matchTypes = [])
$this->setBasePath($basePath); {
$this->addMatchTypes($matchTypes); $this->addRoutes($routes);
} $this->setBasePath($basePath);
$this->addMatchTypes($matchTypes);
}
/** /**
* Retrieves all routes. * Retrieves all routes.
* Useful if you want to process or display routes. * Useful if you want to process or display routes.
* @return array All routes. * @return array All routes.
*/ */
public function getRoutes() { public function getRoutes()
return $this->routes; {
} return $this->routes;
}
/** /**
* Add multiple routes at once from array in the following format: * Add multiple routes at once from array in the following format:
* *
* $routes = array( * $routes = [
* array($method, $route, $target, $name) * [$method, $route, $target, $name]
* ); * ];
* *
* @param array $routes * @param array $routes
* @return void * @return void
* @author Koen Punt * @author Koen Punt
* @throws Exception * @throws Exception
*/ */
public function addRoutes($routes){ public function addRoutes($routes)
if(!is_array($routes) && !$routes instanceof Traversable) { {
throw new \Exception('Routes should be an array or an instance of Traversable'); if (!is_array($routes) && !$routes instanceof Traversable) {
} throw new RuntimeException('Routes should be an array or an instance of Traversable');
foreach($routes as $route) { }
call_user_func_array(array($this, 'map'), $route); foreach ($routes as $route) {
} call_user_func_array([$this, 'map'], $route);
} }
}
/** /**
* Set the base path. * Set the base path.
* Useful if you are running your application from a subdirectory. * Useful if you are running your application from a subdirectory.
*/ * @param string $basePath
public function setBasePath($basePath) { */
$this->basePath = $basePath; public function setBasePath($basePath)
} {
$this->basePath = $basePath;
}
/** /**
* Add named match types. It uses array_merge so keys can be overwritten. * 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. * @param array $matchTypes The key is the name and the value is the regex.
*/ */
public function addMatchTypes($matchTypes) { public function addMatchTypes(array $matchTypes)
$this->matchTypes = array_merge($this->matchTypes, $matchTypes); {
} $this->matchTypes = array_merge($this->matchTypes, $matchTypes);
}
/** /**
* Map a route to a target * 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 $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 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 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. * @param string $name Optional name of this route. Supply if you want to reverse route this url in your application.
* @throws Exception * @throws Exception
*/ */
public function map($method, $route, $target, $name = null) { public function map($method, $route, $target, $name = null)
{
$this->routes[] = array($method, $route, $target, $name); $this->routes[] = [$method, $route, $target, $name];
if($name) { if ($name) {
if(isset($this->namedRoutes[$name])) { if (isset($this->namedRoutes[$name])) {
throw new \Exception("Can not redeclare route '{$name}'"); throw new RuntimeException("Can not redeclare route '{$name}'");
} else { }
$this->namedRoutes[$name] = $route; $this->namedRoutes[$name] = $route;
} }
} return;
}
return; /**
} * 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
* Reversed routing if (!isset($this->namedRoutes[$routeName])) {
* throw new RuntimeException("Route '{$routeName}' does not exist.");
* 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 = array()) {
// Check if named route exists // Replace named parameters
if(!isset($this->namedRoutes[$routeName])) { $route = $this->namedRoutes[$routeName];
throw new \Exception("Route '{$routeName}' does not exist.");
}
// Replace named parameters // prepend base path to route url again
$route = $this->namedRoutes[$routeName]; $url = $this->basePath . $route;
// prepend base path to route url again if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
$url = $this->basePath . $route; foreach ($matches as $index => $match) {
list($block, $pre, $type, $param, $optional) = $match;
if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) { if ($pre) {
$block = substr($block, 1);
}
foreach($matches as $index => $match) { if (isset($params[$param])) {
list($block, $pre, $type, $param, $optional) = $match; // 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);
}
}
}
if ($pre) { return $url;
$block = substr($block, 1); }
}
if(isset($params[$param])) { /**
// Part is found, replace for param value * Match a given Request Url against stored routes
$url = str_replace($block, $params[$param], $url); * @param string $requestUrl
} elseif ($optional && $index !== 0) { * @param string $requestMethod
// Only strip preceeding slash if it's not at the base * @return array|boolean Array with route information on success, false on failure (no match).
$url = str_replace($pre . $block, '', $url); */
} else { public function match($requestUrl = null, $requestMethod = null)
// Strip match block {
$url = str_replace($block, '', $url);
}
}
} $params = [];
return $url; // set Request Url if it isn't passed as parameter
} if ($requestUrl === null) {
$requestUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
}
/** // strip base path from request url
* Match a given Request Url against stored routes $requestUrl = substr($requestUrl, strlen($this->basePath));
* @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) {
$params = array(); // Strip query string (?a=b) from Request Url
$match = false; if (($strpos = strpos($requestUrl, '?')) !== false) {
$requestUrl = substr($requestUrl, 0, $strpos);
}
// set Request Url if it isn't passed as parameter // set Request Method if it isn't passed as a parameter
if($requestUrl === null) { if ($requestMethod === null) {
$requestUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/'; $requestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
} }
// strip base path from request url foreach ($this->routes as $handler) {
$requestUrl = substr($requestUrl, strlen($this->basePath)); list($methods, $route, $target, $name) = $handler;
// Strip query string (?a=b) from Request Url $method_match = (stripos($methods, $requestMethod) !== false);
if (($strpos = strpos($requestUrl, '?')) !== false) {
$requestUrl = substr($requestUrl, 0, $strpos);
}
// set Request Method if it isn't passed as a parameter // Method did not match, continue to next route.
if($requestMethod === null) { if (!$method_match) {
$requestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; continue;
} }
foreach($this->routes as $handler) { if ($route === '*') {
list($methods, $route, $target, $name) = $handler; // * 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;
}
$method_match = (stripos($methods, $requestMethod) !== false); if ($match) {
if ($params) {
foreach ($params as $key => $value) {
if (is_numeric($key)) {
unset($params[$key]);
}
}
}
// Method did not match, continue to next route. return [
if (!$method_match) continue; 'target' => $target,
'params' => $params,
'name' => $name
];
}
}
return false;
}
if ($route === '*') { /**
// * wildcard (matches all) * Compile the regex for a given route (EXPENSIVE)
$match = true; * @param $route
} elseif (isset($route[0]) && $route[0] === '@') { * @return string
// @ regex delimiter */
$pattern = '`' . substr($route, 1) . '`u'; protected function compileRoute($route)
$match = preg_match($pattern, $requestUrl, $params) === 1; {
} elseif (($position = strpos($route, '[')) === false) { if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
// No params in url, do string comparison $matchTypes = $this->matchTypes;
$match = strcmp($requestUrl, $route) === 0; foreach ($matches as $match) {
} else { list($block, $pre, $type, $param, $optional) = $match;
// 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) { if (isset($matchTypes[$type])) {
$type = $matchTypes[$type];
}
if ($pre === '.') {
$pre = '\.';
}
if ($params) { $optional = $optional !== '' ? '?' : null;
foreach($params as $key => $value) {
if(is_numeric($key)) unset($params[$key]);
}
}
return array( //Older versions of PCRE require the 'P' in (?P<named>)
'target' => $target, $pattern = '(?:'
'params' => $params, . ($pre !== '' ? $pre : null)
'name' => $name . '('
); . ($param !== '' ? "?P<$param>" : null)
} . $type
} . ')'
return false; . $optional
} . ')'
. $optional;
/** $route = str_replace($block, $pattern, $route);
* Compile the regex for a given route (EXPENSIVE) }
*/ }
protected function compileRoute($route) { return "`^$route$`u";
if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) { }
$matchTypes = $this->matchTypes;
foreach($matches as $match) {
list($block, $pre, $type, $param, $optional) = $match;
if (isset($matchTypes[$type])) {
$type = $matchTypes[$type];
}
if ($pre === '.') {
$pre = '\.';
}
$optional = $optional !== '' ? '?' : null;
//Older versions of PCRE require the 'P' in (?P<named>)
$pattern = '(?:'
. ($pre !== '' ? $pre : null)
. '('
. ($param !== '' ? "?P<$param>" : null)
. $type
. ')'
. $optional
. ')'
. $optional;
$route = str_replace($block, $pattern, $route);
}
}
return "`^$route$`u";
}
} }