A small, simple, extendable one-file PHP router with groups, filters and named routes
I'm not claiming that this router is faster or better than other routers out there. It's kind of hard to beat something like nikic/FastRoute. The two main reasons for building this was: 1. I wanted a simple but yet flexible, plug and play router with minimal to none setup. 2. It's fun to build stuff and you learn a lot from it!
- Install
- Simple Example
- Route parameters
- Route callbacks
- Filters
- Named routes
- Grouping routes
- Crud routes
- Redirects
- Dispatch the router
- Adding a custom callback resolver
- Release notes
Clone this repository or use composer to download the library with the following command:
composer require maer/router dev-master
Change dev-master
to the last tagged release.
// Load composers autoloader
include '/path/to/vendor/autoload.php';
$r = new Maer\Router\Router;
// Define routes
$r->get('/', function() {
return "Hello there";
});
// It also works with:
$r->post('/', function() {});
$r->put('/', function() {});
$r->delete('/', function() {});
$r->patch('/', function() {});
$r->options('/', function() {});
$r->head('/', function() {});
$r->connect('/', function() {});
$r->trace('/', function() {});
$r->any('/', function() {}); // Catches all methods
// ...or if you want to use some non-standard HTTP verb
$r->add('SOMEVERB', '/', function() {});
// ...or if you want to define multiple verbs at once
$r->add(['GET', 'POST', ...], function() {});
// Dispatch the router
$response = $r->dispatch();
echo $response;
There are some placeholders you can use for route parameters. All parameters will be passed along to the controller in the same order as they are defined in the route:
// Match any alpha [a-z] character
$r->get('/something/(:alpha)', function($param) {
// Do stuff
});
// Match any numeric [0-9.,] character. It can also start with a -
$r->get('/something/(:num)', function($param) {
// Do stuff
});
// Match any alphanumeric [a-z0-9] character
$r->get('/something/(:alphanum)', function($param) {
// Do stuff
});
// Match any character (except /) [^/] character
$r->get('/something/(:any)', function($param) {
// Do stuff
});
// Catch-all. Match all routes, including / (.*)
$r->get('/something/(:any)', function($param) {
// Do stuff
});
// Append ? to making a parameter optional.
$r->get('/something/(:alpha)?', function($param = null){
// Matches /something and /something/anything
});
// Combine mutliple placeholders
$r->get('/something/(:alpha)/(:any)/(:alphanum)?', function($param, $param2, $param3 = null) {
// Do stuff
});
Route callbacks can be defined in different ways:
// Anonymous function
$r->get('/', function() {
// Something
});
// Class method
$r->get('/', ['Namespace\ClassName', 'methodName']);
// or
$r->get('/', 'Namespace\ClassName@methodName');
// Static class method
$r->get('/', 'Namespace\ClassName::methodName');
All callbacks will receive any route parameter.
If you send in a class method (non static), the router will instantiate the class and then call the method, when the router is dispatched and a match is found.
There are before
and after
filters:
// Defining filters
$r->filter('myfilter', function() {
// Do some magic stuff.
});
$r->filter('anotherfilter', function() {
// Do some magic stuff.
});
// Add filter to your routes
$r->get('/something/', function() {
}, ['before' => 'myfilter', 'after' => 'anotherfilter']);
// Add multiple filters by combining them with |
$r->get('/something/', function() {
}, ['before' => 'myfilter|anotherfilter']);
Filter callbacks can be in the same formats as Route callbacks, meaning that you can use class methods as filters.
The before filter will receive all route parameter, just like the route callback. The after filter will also receive all parameters, but the first parameter will be be the response from the route callback.
Note: Filters will be called in the same order as they were defined. If any filter returns anything other than null
, the dispatch will stop and that response will be returned instead.
Add a name to any route
// Name a route
$r->get('/something', function() {
}, ['name' => 'some-page']);
// Resolve a named route
echo $r->getRoute('some-page');
// Returns: /something
// With route parameters
$r->get('/something/(:any)/(:any)', function($param1, $param2) {
});
// Resolve and pass values for the placeholders
echo $r->getRoute('some-page', ['first', 'second']);
// Returns: /something/first/second
If you don't pass enough arguments to cover all required parameters, an exception will be thrown.
By default, the router returns paths without the base url (protocol + hostname)
However, if you want the response from getRoute()
to include the base url, you need to set the base url:
$r->baseUrl('http://foo.bar');
When you now want to get a named route, you can pass a boolean as third argument. true
= include base url and false
won't.
$r->getRoute('some-page', [], true);
// Returns: http://foo.bar/something
If you don't want to pass a third argument, but rather have it returning the base url as default, you can set that using:
$r->alwaysPrependBaseUrl(true);
If you call getRoute()
without the third argument, it will always prepend the base url unless you pass false
as third argument to getRoute()
.
Instead of adding the same filters over and over for many routes, it's easier to group them together.
$r->group(['before' => 'a_before_filter'], function($r) {
$r->get('/', function() {
//....
});
// Just keep defining routes
});
The $r->group()
-method only takes an anonymous function as callback. The router instance is always passed as an argument to the callback.
When defining a group, you can add before
and after
filters, just like you do for routes. You can also use a prefix
for a group, as described below.
To add the same prefix to a group, use the prefix
argument.
$r->group(['prefix' => '/admin'], function() {
// This matches: /admin
$r->get('/', function() {
});
// This matches: /admin/something
$r->get('/something', function() {
});
});
You can mix before
, after
and prefix
when creating groups.
To simplify the creation of CRUD routes, there's a crud()
-function.
$r->crud('/posts', 'PostsController', [
'name' => 'posts',
]);
The above is the same as if you would define the following routes:
$r->get('/posts', 'PostsController@many', [
'name' => 'posts.many'
]);
$r->get('/posts/(:any)', 'PostsController@one', [
'name' => 'posts.one'
]);
$r->post('/posts', 'PostsController@create', [
'name' => 'posts.create'
]);
$r->put('/posts/(:any)', 'PostsController@update', [
'name' => 'posts.update'
]);
$r->delete('/posts/(:any)', 'PostsController@delete', [
'name' => 'posts.delete'
]);
You can of course use the crud()
function inside a group as well.
You can register the router to redirect certain URL's as well.
Note: All redirect routes are triggered before any other registered routes.
Register a redirect:
// A simple redirect
$r->redirect('/from/path', '/to/path');
// It works with absolute targets as well
$r->redirect('/foo', 'https://example.com');
// You can also register a named route as target
$r->redirect('/foo', null, [
'route' => ['someRouteName'],
// Add route arguments, if needed
'params' => ['argument1', 'argument2', ...]
]);
// If you want to redirect using another http code than 307 (default)
$r->redirect('/foo', '/bar', [
'code' => 301
]);
You can also register before
-filters on a redirect.
When you register i redirect using the above method, it will be handled on dispatch, just like any other route.
Sometimes you want to redirect the user straight away, maybe after the router already has been dispatched (or even before). You can use toRoute()
for that:
// Make a redirect to a named route
$r->toRoute('someRouteName', [
'argument1',
'argument2',
...
]);
// With another http code than 307 (default)
$r->toRoute('someRouteName', [], 301);
The above will redirect the request immediately. It's just like doing header('location: ...')
.
To dispatch the router, it's usually enough to just call the $r->dispatch()
-method. How ever, if you want to dispatch the router with some specific URL and method you can pass them to the dispatcher (this is useful if you're writing tests):
$response = $r->dispatch('GET', '/some/url');
If you rather trigger all the callbacks (filters and route callbacks) yourself, if you, for example, are using an IoC container, call the $r->getMatch()
method instead and you will get the matched route object back.
$r->get('/', function() {
}, ['before' => 'beforefilter', 'after' => 'afterfilter', 'name' => 'somename']);
$route = $r->getMatch('GET', '/');
// Returns:
// object =>
// pattern => "/group1"
// name => "somename",
// callback => object(Closure)#8 (0) {}
// before => ['beforefilter'],
// after => ['afterfilter'],
// args => []
// method => "GET"
If the before and after filters are closures, you can trigger them via:
$response = $r->executeCallback('beforefilter');
If there is no match, a Maer\Router\NotFoundException
will be thrown. You can register a callback that will be executed instead, using the $router->notFound()
-method:
$r->notFound(function() {
return "Ops! The page was not found!";
});
// Callbacks can be in all the same formats as for routes
If there is a url match but with the wrong http verb, a Maer\Router\MethodNotAllowedException
will be thrown. You can register a callback that will be executed instead, using the $router->methodNotAllowed()
-method:
$r->methodNotAllowed(function() {
return "Ops! Method not allowed!";
});
// Callbacks can be in all the same formats as for routes
If your callback is in the format of ['Classname', 'method']
, you might want to customize how it's resolved. This is handy if you, for example, are using some kind of IoC with dependency injection.
To create your custom resolver, use the $r->resolver()
-method. Example:
$r->resolver(function($callback) use($container) {
// The argument will always be an array with ['Class', 'method']
return [
$container->get($callback[0]),
$container[1]
];
});
- Added redirect-method redirect()
- Added redirect-method toRoute()
- Added crud()-method.
If you have any questions, suggestions or issues, let me know!
Happy coding!