diff options
-rw-r--r-- | README.md | 62 | ||||
-rw-r--r-- | router.php | 170 |
2 files changed, 232 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..0526bf6 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# php-router + +Just another PHP router. Simple and fast. + +## Usage + +#### Route syntax + +`{}` is for enumeration of predefined strings, like: `{value1,value2,value3}`. + +`()` is for regular expressions, like: `([\w\d]+)`. + +See examples. + +#### Code example + +``` +$router = new router; + +// main page, returned value will be `index` +$router->add('/', 'index'); + +// for `/test2.html`, returned value will be `test_html id=2` +$router->add('/test(\d).html', 'test_html id=$(1)'); + +// for `/contacts/`, returned value will be `contacts` +$router->add('{contacts,about}/', '${1}'); + +// for `/section2/123/action1/`, returned value will be 'section section=section2_123 action=action1' +$router->add('{section1,section2}/(\d+)/{action1,action2}/', 'section section=${1}_$(1) action=${2}'); + +$route = $router->find($_SERVER['DOCUMENT_URI']); +if ($route === false) { + // NOT FOUND +} + +// parse value +$route = preg_split('/ +/', $route); +$page = $route[0]; +$input = []; +if (count($route) > 1) { + for ($i = 1; $i < count($route); $i++) { + $var = $route[$i]; + list($k, $v) = explode('=', $var); + $input[$k] = $v; + } +} +``` + +## API + +`add($route, $value)` - Add route. `$value` is a string in any form. See examples below. + +`find($route)` - Find route. Returns compiled `$value` or `false` if not found. + +`dump()` - Returns internal routes tree (for serialization, e.g. for production usage). + +`load($tree)` - Loads routes tree, previously returned by `dump()`. + +## License + +MIT diff --git a/router.php b/router.php new file mode 100644 index 0000000..198b48f --- /dev/null +++ b/router.php @@ -0,0 +1,170 @@ +<?php + +class router { + + protected $routes = [ + 'children' => [], + 're_children' => [] + ]; + + public function add($template, $value) { + if ($template == '') { + return; + } + + // expand {enum,erat,ions} + $templates = [[$template, $value]]; + if (preg_match_all('/\{([\w\d_\-,]+)\}/', $template, $matches)) { + foreach ($matches[1] as $match_index => $variants) { + $variants = explode(',', $variants); + $variants = array_map('trim', $variants); + $variants = array_filter($variants, function($s) { return $s != ''; }); + + for ($i = 0; $i < count($templates); ) { + list($template, $value) = $templates[$i]; + $new_templates = []; + foreach ($variants as $variant_index => $variant) { + $new_templates[] = [ + str_replace_once($matches[0][$match_index], $variant, $template), + str_replace('${'.($match_index+1).'}', $variant, $value) + ]; + } + array_splice($templates, $i, 1, $new_templates); + $i += count($new_templates); + } + } + } + + // process all generated routes + foreach ($templates as $template) { + list($template, $value) = $template; + + $start_pos = 0; + $parent = &$this->routes; + $template_len = strlen($template); + + while ($start_pos < $template_len) { + $slash_pos = strpos($template, '/', $start_pos); + if ($slash_pos !== false) { + $part = substr($template, $start_pos, $slash_pos-$start_pos+1); + $start_pos = $slash_pos+1; + } else { + $part = substr($template, $start_pos); + $start_pos = $template_len; + } + + $parent = &$this->_addRoute($parent, $part, + $start_pos < $template_len ? null : $value); + } + } + } + + protected function &_addRoute(&$parent, $part, $value = null) { + $par_pos = strpos($part, '('); + $is_regex = $par_pos !== false && ($par_pos == 0 || $part[$par_pos-1] != '\\'); + + $children_key = !$is_regex ? 'children' : 're_children'; + + if (isset($parent[$children_key][$part])) { + if (is_null($value)) { + $parent = &$parent[$children_key][$part]; + } else { + if (!isset($parent[$children_key][$part]['value'])) { + $parent[$children_key][$part]['value'] = $value; + } else { + trigger_error(__METHOD__.': route is already defined'); + } + } + return $parent; + } + + $child = [ + 'children' => [], + 're_children' => [] + ]; + if (!is_null($value)) { + $child['value'] = $value; + } + + $parent[$children_key][$part] = $child; + return $parent[$children_key][$part]; + } + + public function find($uri) { + if ($uri != '/' && $uri[0] == '/') { + $uri = substr($uri, 1); + } + $start_pos = 0; + $parent = &$this->routes; + $uri_len = strlen($uri); + $matches = []; + + while ($start_pos < $uri_len) { + $slash_pos = strpos($uri, '/', $start_pos); + if ($slash_pos !== false) { + $part = substr($uri, $start_pos, $slash_pos-$start_pos+1); + $start_pos = $slash_pos+1; + } else { + $part = substr($uri, $start_pos); + $start_pos = $uri_len; + } + + $found = false; + if (isset($parent['children'][$part])) { + $parent = &$parent['children'][$part]; + $found = true; + } else if (!empty($parent['re_children'])) { + foreach ($parent['re_children'] as $re => &$child) { + if (preg_match('#^'.$re.'$#', $part, $match)) { + if (count($match) > 1) { + $matches = array_merge($matches, array_slice($match, 1)); + } + $parent = &$child; + $found = true; + break; + } + } + } + + if (!$found) { + return false; + } + } + + if (!isset($parent['value'])) { + return false; + } + + $value = $parent['value']; + if (!empty($matches)) { + foreach ($matches as $i => $match) { + $needle = '$('.($i+1).')'; + $pos = strpos($value, $needle); + if ($pos !== false) { + $value = substr_replace($value, $match, $pos, strlen($needle)); + } + } + } + + return $value; + } + + public function load($routes) { + $this->routes = $routes; + } + + public function dump() { + return $this->routes; + } + +} + +if (!function_exists('str_replace_once')) { + function str_replace_once($needle, $replace, $haystack) { + $pos = strpos($haystack, $needle); + if ($pos !== false) { + $haystack = substr_replace($haystack, $replace, $pos, strlen($needle)); + } + return $haystack; + } +} |