Router
This PHP class matches url against predefined route patterns. Routes follow simple DSL syntax, Router converts them to regular expressions and performs match on a first-match basis.
Short codes
Router uses the following short codes for the common patterns:
- $ - match the string according to php variable syntax ([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)
- : - match alphanumeric string ([A-Za-z0-9]+)
- # - match numeric string ([0-9]+)
- * - wildcard match (.*)
- ~ - match extension ([a-z]{1,5})
- ^ - match alphanumeric with hyphen ([A-Za-z0-9\-]+)
Examples
$router = new Router(); $router->add( 'controller-only', '/$controller' ); $result = $router->match('/news'); Result: Array ( [url] => Array ( [controller] => news ) [id] => controller-only [data] => Array ( [controller] => news ) )
Named placeholders should consist from uppercase and lowercase letters and underscores only, without spaces, hyphens etc. e.g. $controller, :action, #some_id
$router = new Router(); $router->add( 'controller-and-action', '/$my_controller/$action' ); $result = $router->match('/news/add'); Result: Array ( [url] => Array ( [my_controller] => news [action] => add ) [id] => controller-and-action [data] => Array ( [my_controller] => news [action] => add ) )
Optional parameters example
$router = new Router(); $router->add( 'optional-controller-and-action', '/($controller(/$action))', array( 'controller' => 'index', 'action' => 'index' ) ); $result = $router->match('/news/add'); Result: Array ( [url] => Array ( [controller] => news [action] => add ) [id] => optional-controller-and-action [data] => Array ( [controller] => news [action] => add ) ) $result = $router->match('/news'); Result: Array ( [url] => Array ( [controller] => news ) [id] => optional-controller-and-action [data] => Array ( [controller] => news [action] => index ) ) $result = $router->match('/'); Result: Array ( [url] => Array ( ) [id] => optional-controller-and-action [data] => Array ( [controller] => index [action] => index ) )
When using optional parameters, it makes sense to provide an array of default values as a third parameter to Router::add method, as in the above example
If url contains query string, it will be appended to the resulting data array:
$router = new Router(); $router->add( 'optional-controller-and-action', '/($controller)(/$action)', array( 'controller' => 'index', 'action' => 'index' ) ); $result = $router->match('/news/add?slug=some-slug&id=12'); Result: Array ( [url] => Array ( [controller] => news [action] => add ) [id] => optional-controller-and-action [data] => Array ( [slug] => some-slug [id] => 12 [controller] => news [action] => add ) )
It is possible to use in place regular expressions:
$router = new Router(); $router->add( 'in-place-regex', '/($controller<[A-Z]{2}>)(/$action)', array( 'controller' => 'index', 'action' => 'index' ) ); $result = $router->match('/news/add'); Result (empty array - no match): Array ( ) $result = $router->match('/AB/add'); Array ( [url] => Array ( [controller] => AB [action] => add ) [id] => in-place-regex [data] => Array ( [controller] => AB [action] => add ) )
Other examples
$router = new Router(); $router->add( 'article-with-slug', '/$controller-$action(/^slug)', array( 'controller' => 'index', 'action' => 'index', 'format' => 'html' ) ); $result = $router->match('/news-add/some-article-title'); Result: Array ( [url] => Array ( [controller] => news [action] => add [slug] => some-article-title ) [id] => article-with-slug [data] => Array ( [controller] => news [action] => add [format] => html [slug] => some-article-title ) )
$router = new Router(); $router->add( 'article-with-date-and-slug', '(/$controller)(/$action(.~format))(/#year-#month-#day)(/^slug)', array( 'controller' => 'index', 'action' => 'index', 'format' => 'html' ) ); $result = $router->match('/articles/2009-01-01/some-slug-for-article'); Result: Array ( [url] => Array ( [controller] => articles [year] => 2009 [month] => 01 [day] => 01 [slug] => some-slug-for-article ) [id] => article-with-date-and-slug [data] => Array ( [controller] => articles [action] => index [format] => html [year] => 2009 [month] => 01 [day] => 01 [slug] => some-slug-for-article ) )
As the Router works on a first-match basis, it's recommended to define routes in order of specificity, from most specific to general ones.
$router = new Router(); $router->add( 'controller-action-id', '/$controller/$action/#id' ); $router->add( 'controller-action', '/$controller/$action' ); $router->add( 'controller', '/$controller' );
Predefined routes
It's possible to have predefined routes in some php file, and provide them to Router as a parameter for constructor:
[routes.php] <?php return array( 'controller-action-id' => array( 'route' => '/$controller/$action/#id' ), 'controller-action' => array( 'route' => '/$controller/$action' ), 'general' => array( 'route' => '(/$controller)(/$action(.:format<[a-z]{2,4}>))(/#id)(/:slug<[A-Za-z0-9\-]+>)', 'defaults' => array( 'controller' => 'index', 'action' => 'index', 'format' => 'html', 'id' => 1, 'slug' => 'default-slug' ) ) ); ?> [index.php] <?php $routes = include 'routes.php'; $router = new Router($routes); $result = $router->match('/news/add.xml/12/some-slug'); $result = $router->match('/news/add/12'); $result = $router->match('/news/add'); ?>
Using Router compile method it's possible to get php code of routes array with compiled regular expressions, so Router will skip route to regex conversion
$router->compile(); [output] <?php return array ( 'controller-action-id' => array ( 'route' => '/$controller/$action/#id', 'regex' => '/^\/(?P<controller>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\/(?P<action>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\/(?P<id>[0-9]+)$/D', ), 'controller-action' => array ( 'route' => '/$controller/$action', 'regex' => '/^\/(?P<controller>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\/(?P<action>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$/D', ), 'general' => array ( 'route' => '(/$controller)(/$action(.:format<[a-z]{2,4}>))(/#id)(/:slug<[A-Za-z0-9\-]+>)', 'defaults' => array ( 'controller' => 'index', 'action' => 'index', 'format' => 'html', 'id' => 1, 'slug' => 'default-slug', ), 'regex' => '/^(?:\/(?P<controller>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*))?(?:\/(?P<action>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)(?:\.(?P<format>[a-z]{2,4}))?)?(?:\/(?P<id>[0-9]+))?(?:\/(?P<slug>[A-Za-z0-9\-]+))?$/D', ), ); ?>
URL generation
URLs can be generated using Router::url() method, which accepts 3 arguments:
- $params - associative array of keys and values that should be replaced
- $route - matched route (optional, if not provided, the last matched route will be used)
- $type - can be:
- 'url' (default) - $params will be merged with the route url array
- 'full' (default) - $params will be merged with the route full data array
- 'self' - no merge will be performed
For example, Router matched the following:
$route = array( 'id' => 'article-with-date-and-slug', 'url' => array( 'controller' => 'articles', 'action' => 'index', 'year' => '2009' ), 'data' => array( 'controller' => 'articles', 'action' => 'index', 'format' => 'html', 'year' => '2009', 'month' => '01', 'day' => '01', 'slug' => 'some-slug-for-article' ) );
URL generated with 'full' type set:
var_dump( $router->url( array( 'controller' => 'test', 'action' => 'view', 'format' => 'json', 'year' => '2010', 'month' => '02', 'asdf' => 'asd' // non-existing parameter, will be filtered ), $route, 'full' ) ); Result: '/test/view.json/2010-02-01/some-slug-for-article'
URL generated with 'url' type set:
var_dump( $router->url( array( 'controller' => 'test', 'action' => 'view', 'format' => 'json', 'asdf' => 'asd' // non-existing parameter, will be filtered ), $route, 'url' ) ); Result: '/test/view.json/2009'
URL generated with 'self' type set:
var_dump( $router->url( array( 'controller' => 'test', 'action' => 'view', 'format' => 'json', 'asdf' => 'asd' // non-existing parameter, will be filtered ), $route, 'self' ) ); Result: '/test/view.json'