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'