A simple & powerful router for PHP 5.4+
V1.3
V1.2
- ClosureTemplate resolver
- ControllerTemplate resolver
- Possibility to choose a resolver
V1.1
V1.0
- Support static & dynamic route patterns
- Support REST routing
- Support reversed routing using named routes
- Uri matcher
- Array matcher
- Closure resolver
- Template resolver
- Controller resolver
- Route Middleware
- Integration with other libraries
- PHP 5.4+ is required
- Install
JetFire\Routing
using Composer - Setup URL rewriting so that all requests are handled by index.php
Via composer
$ composer require jetfirephp/routing
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.[a-zA-Z0-9\-\_\/]*)$ index.php?url=$1 [QSA,L]
Create an instance of JetFire\Routing\RouteCollection
and define your routes. Then create an instance of JetFire\Routing\Router
and run your routes.
// Require composer autoloader
require __DIR__ . '/vendor/autoload.php';
// Create RouteCollection instance
$collection = new \JetFire\Routing\RouteCollection();
// Define your routes
// ...
// Create an instance of Router
$router = new \JetFire\Routing\Router($collection)
// select your matcher
$matcher1 = new \JetFire\Routing\Matcher\ArrayMatcher($router);
$matcher2 = new \JetFire\Routing\Matcher\UriMatcher($router);
// set your matcher to the router
$router->setMatcher([$matcher1,$matcher2])
// Run it!
$router->run();
JetFire\Routing
provide 2 type of matcher for your routes : JetFire\Routing\Matcher\ArrayMatcher
and JetFire\Routing\Matcher\UriMatcher
With Uri Matcher you don't have to define your routes. Depending on the uri it can check if a target exist for the current url. But you have to define your views directory path and controllers namespace to the collection :
$options = [
'view_dir' => '_VIEW_DIR_PATH_',
'ctrl_namespace' => '_CONTROLLERS_NAMESPACE_'
];
$collection = new \JetFire\Routing\RouteCollection(null,$options);
// or
$collection = new \JetFire\Routing\RouteCollection();
$collection->setOption($options);
// or
$collection = new \JetFire\Routing\RouteCollection();
$collection->addRoutes(null,$options)
For example if the uri is : /home/index
Here are the list of Uri Matcher resolver :
$resolver = [
'isControllerAndTemplate',
'isController',
'isTemplate'
];
Uri Matcher check if an index.php
file exist in /_VIEW_DIR_PATH_/Home
directory.
If you want to check for other extension (html,json,...) You can configure the router like this :
$router->setConfig([
// Define your template extension like this
'templateExtension' => ['.php','.html','.twig','.json','.xml'],
]);
With Controller resolver, Uri Matcher checks if a controller with name HomeController
located in the namespace _CONTROLLERS_NAMESPACE_
has the index
method.
You have to require your controller before matching or you can use your custom autoloader to load your controllers.
Uri Matcher support also dynamic routes. For example if the uri is : /home/user/peter/parker
then you must have a method user
with two parameters like this :
class HomeController {
public function user($firstName,$lastName){
// $firstName = peter
// $lastName = parker
}
}
With Array Matcher you have to add your routes like this :
$options = [
'view_dir' => '_VIEW_DIR_PATH_',
'ctrl_namespace' => '_CONTROLLERS_NAMESPACE_'
];
// addRoutes expect an array argument
$collection->addRoutes([
'/home/index' => '_TARGET_'
],$options);
// or a file containing an array
$collection->addRoutes('path_to_array_file',$options);
We recommend that you define your routes in a separate file and pass the path to addRoutes()
method.
// routes.php file
return [
'/home/index' => '_TARGET_'
];
Here are the list of Uri Matcher resolver :
$resolver = [
'isControllerAndTemplate',
'isClosureAndTemplate',
'isClosure',
'isController',
'isTemplate'
];
You have 5 actions possible for Array Routing. We assume you are using a separate file for your routes.
return [
// static route
'/home/index' => 'Home/index.php',
// dynamic route with arguments
'/home/user-:id-:slug' => [
'use' => 'Home/page.html',
'arguments' => ['id' => '[0-9]+','slug' => '[a-z-]*'],
],
];
return [
// static route
'/home/index' => 'HomeController@index',
// dynamic route with arguments
'/home/user-:id-:slug' => [
'use' => 'HomeController@page',
'arguments' => ['id' => '[0-9]+','slug' => '[a-z-]*'],
],
];
return [
// controller and template resolver
// call first the controller and render then the template
// if the template is not found, the controller is returned
'/home/log' => [
'use' => 'HomeController@log',
'template' => 'Home/log.php', //in your controller you can return an array of data that you can access in your template
],
// dynamic route with arguments
'/home/user-:id-:slug' => [
'use' => 'HomeController@page',
'template' => 'Home/log.php',
'arguments' => ['id' => '[0-9]+','slug' => '[a-z-]*'],
],
];
return [
// suppose we have the following methods in the AccountController :
// public function create();
// public function read($id);
// public function update($id);
// public function destroy($id);
// if the uri is /account/create the router will call the associated method in the controller
'/account/*' => [
'use' => 'AccountController@{method}',
],
];
return [
// static route
'/home/index' => function(){
return 'Hello world !';
},
// dynamic route with arguments
'/home/user-:id-:slug' => [
'use' => function($id,$slug){
return 'Hello User '.$id.'-'.$slug;
},
'arguments' => ['id' => '[0-9]+','slug' => '[a-z-]*'],
],
];
return [
// closure and template matching
// call first the closure and render then the template
'/home/log' => [
'use' => function(){
return ['name' => 'Peter'];
}
'template' => 'Home/log.php', // in log.php you can access the return data like this : $name ='Peter'
],
'/home/user-:id-:slug' => [
'use' => function($id,$slug){
return ['id' => $id,'slug' => $slug];
},
'template' => 'Home/log.php',
'arguments' => ['id' => '[0-9]+','slug' => '[a-z-]*'],
],
];
With JetFire\Routing
you have the ability to create block routes to better organize your code.
For example , if you have an administration for your website , you can create block only for this section and another block to the public part like this :
// Create RouteCollection instance
$collection = new \JetFire\Routing\RouteCollection();
// Block routes
$collection->addRoutes('admin_routes_path',['view_dir' => 'admin_view_path' , 'ctrl_namespace' => 'admin_controllers_namespace','prefix' => 'admin']);
$collection->addRoutes('public_routes_path',['view_dir' => 'public_view_path' , 'ctrl_namespace' => 'public_controllers_namespace']);
// Create an instance of Router
$router = new \JetFire\Routing\Router($collection)
// Select your matcher
$router->addRouter(new \JetFire\Routing\Matcher\ArrayMatcher($router));
// Run it!
$router->run();
Here are the list of router configuration that you can edit :
$router->setConfig([
// You can add/remove extension for views
// default extension for views
'templateExtension' => ['.html', '.php', '.json', '.xml'],
// If you use template engine library, you can use this to render the view
// See the 'Integration with other libraries' section for more details
'templateCallback' => [],
// If you want to add a dependency injection container for your controllers constructor or method
// for example if your controller 'HomeController' method 'log' method require a class like this : public function log(Request $request)
// by default :
'di' => function($class){
return new $class;
},
// See the Named Routes section for more details
'generateRoutesPath' => false,
]);
Here are the list of options that you can edit for each collection routes :
$options = [
// your view directory
'view_dir' => 'view_directory',
// your controllers namespace
'ctrl_namespace' => 'controllers_namespace',
// your routes prefix
'prefix' => 'your_prefix'
];
You can specify a name for each route like this :
return [
'/home/index' => [
'use' => 'Home/index.html',
'name' => 'home.index'
],
'/home/user-:id-:slug' => [
'use' => 'HomeController@user',
'name' => 'home.user',
'arguments' => ['id' => '[0-9]+','slug' => '[a-z-]*'],
],
];
And then to get the url of this route you can do like this :
// You have to enable generateRoutesPath to get routes url
$router->setConfig([
'generateRoutesPath' => true,
// Other configuration
// ...
]);
// Reverse routing
$collection->getRoutePath('home.index'); // return http://your_domain/home/index
$collection->getRoutePath('home.user',[ 'id' => 1, 'slug' => 'toto']); // return http://your_domain/home/user-1-toto
Supported only in JetFire\Routing\Matcher\ArrayMatcher
.
You can specify the request method for each route like this :
return [
'/api/users' => [
'use' => [
'GET' => function($response){
$response->setHeaders(['Content-Type' => 'application/json']);
return ['name' => 'Peter'];
},
'POST' => function($response){
$response->setHeaders(['Content-Type' => 'application/json']);
$response->setStatusCode(201);
return [];
},
],
'name' => 'api.users',
'method' => ['GET', 'POST']
],
'/api/users/:id' => [
'use' => [
'GET' => function($response){
$response->setHeaders(['Content-Type' => 'application/json']);
return ['name' => 'Peter'];
},
'PUT' => function($response){
$response->setHeaders(['Content-Type' => 'application/json']);
return [];
},
],
'name' => 'api.user',
'arguments' => ['id' => '[0-9]+'],
'method' => ['GET','PUT']
],
];
You can set a prefix for each routes collection like this :
$collection->addRoutes('routes_file_1',['prefix' => 'prefix_1']); // all routes in routes_file_1 begin with prefix_1/
$collection->addRoutes('routes_file_2',['prefix' => 'prefix_2']); // all routes in routes_file_2 begin with prefix_2/
Or :
$collection->addRoutes('routes_file_1');
$collection->addRoutes('routes_file_2');
$collection->setPrefix(['prefix_1','prefix_2']);
Middlewares are called before and after a route match the current uri. You have to create a middleware config file like this :
// Your middleware file
return [
// global middleware are called every time
'global_middleware' => [
// Here you define your middleware class to be called
'app\Middleware\Global',
],
// block middleware are called when the current route block match one of the following block
'block_middleware' => [
// You define here for each block the middleware class to be called
'/app/Blocks/PublicBlock/' => 'app\Middleware\Public',
'/app/Blocks/AdminBlock/' => 'app\Middleware\Admin',
'/app/Blocks/UserBlock/' => 'app\Middleware\User',
],
// class middleware are called when the controller router match one of the following controller
'class_middleware' => [
// You define here for each controller the middleware class to be called
'/app/Blocks/PublicBlock/Controllers/HomeController' => 'app\Middleware\Home',
],
// route middleware are called when the current route match one of the following middleware name
'route_middleware' => [
// You define here a name to the middleware and assign the class to be called
// You have to specify this name to the route like this : `'middleware' => 'home'`
'home' => 'app\Middleware\App'
],
];
Then you have to instantiate the middleware class Middleware
like this :
$middleware = new Middleware($router);
$middleware->setCallbackAction('before', 'your_before_middleware_file');
$middleware->setCallbackAction('between', 'your_between_middleware_file');
$middleware->setCallbackAction('after', 'your_after_middleware_file');
$router->addMiddleware($middleware);
Let see how to create your Middleware Class. For example we take the Global middleware :
namespace app\Middleware;
class Global{
// Middleware class must implements handle method
// object passed in argument will be inject automatically
public function handle(){
// here you put your code
// ...
}
}
See the API section to learn how to handle your $route in middleware class.
If the default matcher and dispatcher doesn't match your expectation, you can write your own matcher and dispatcher like this :
class MyCustomMatcher implements MatcherInterface{
public function __construct(Router $router);
// in this method you can check if the current uri match your expectation
// return true or false
// if it match you have to set your route target with an array of params and the dispatcher class name to be called
// $this->setTarget(['dispatcher' => '\My\Custom\Dispatcher\Class\Name', 'other_params' => 'values']);
public function match();
// set your route target $this->router->route->setTarget($target);
public function setTarget($target = []);
// set your resolver
public function setResolver($resolver = []);
// you can add multiple resolver method in the same matcher
public function addResolver($resolver);
// to retrieve your resolver
public function getResolver();
// dispatcher yo be called
public function setDispatcher($dispatcher = []);
public function addDispatcher($dispatcher);
}
class MyCustomDispatcher implements DispatcherInterface{
public function __construct(Router $router);
// your target to call
// you can get your route target information with $this->route->getTarget()
public function call();
}
$router->addMatcher('MyCustomMatcher');
You can also override the default matcher like this :
class MyCustomMatcher extends ArrayMatcher implements MatcherInterface{
public function __construct(Router $router){
parent::__construct($router);
// your custom match method
$this->addResolver('customResolver');
}
public function customResolver(){
// your code here
// ...
// then you set the route target with the dispatcher
}
}
class MyCustomDispatcher implements DispatcherInterface{
public function __construct(Router $router);
// your target to call
// you can get your route target information with $this->route->getTarget()
public function call();
}
If you want to integrate other template engine libraries like twig, smarty ... you have to set the 'templateCallback' in router.
// Twig template engine
require_once '/path/to/lib/Twig/Autoloader.php';
Twig_Autoloader::register();
// Other template engine
$tpl = new \Acme\Template\Template();
$router->setConfig([
'templateCallback' => [
// if the router find a template with twig enxtension then it will call the twig template engine
'twig' => function($route){
$loader = new Twig_Loader_Filesystem($route->getTarget('block'));
$twig = new Twig_Environment($loader, []);
$template = $twig->loadTemplate($route->getTarget('template'));
echo $template->render($route->getData());
},
// for other template engine
'tpl' => function($route) use ($tpl){
$tpl->load($route->getTarget('template'));
$tpl->setData($route->getData());
$tpl->display();
}
],
// Other configuration
// ...
]);
return [
'{subdomain}.{host}/home' => [
'use' => 'AdminController@index',
'name' => 'admin.home.index',
'subdomain' => 'admin' // could be a regex for multiple subdomain
]
];
Or if you want to add a subdomain for a bloc, you have to add this line in your route collection options :
$options = [
// ...
'subdomain' => 'your_subdomain'
];
Below is a list of the public methods and variables in the common classes you will most likely use.
// JetFire\Routing\RouteCollection
$collection->
routesByName // routes url by their name
countRoutes // count routes block
addRoutes($collection,$options = []) // set your routes
getRoutes($key = null) // return all routes
getRoutePath($name,$params = []) // return the url of route
setPrefix($prefix) // $prefix can be a string (applied for every collection)
// or an array (for each collection you can specify a prefix)
setOption($options = []) // set your routes option
generateRoutesPath() // generate routes url by their name
// JetFire\Routing\Router
$router->
response // JetFire\Routing\ResponseInterface instance
route // JetFire\Routing\Route instance
collection // JetFire\Routing\RouteCollection instance
middlewareCollection // middleware collection
matcher // list of matcher
dispatcher // the dispatcher instance
setConfig($config) // router configuration
getConfig() // get router configuration
run() // run the router with the request url
// JetFire\Routing\Route
$route->
set($args = []) // set your route array parameters
getUrl() // return the route url
setUrl($url) // set the route url
getName() // return the route name
setName($name) // set the route name
getCallback() // return the route callback (template,controller or anonymous function)
setCallback($callback) // set the route callback
getMethod() // return the route method (GET,POST,PUT,DELETE)
getDetail() // return the route detail
setDetail($detail) // set the route detail
addDetail($key,$value) // add a detail for the route
getTarget($key = null) // return the route target (dispatcher,template or controller|action or function)
setTarget($target = []) // set the route target
hasTarget($key = null) // check if the route has the following target
getData() // return data for the route
__call($name,$arguments) // magic call to get or set route detail
The JetFire Routing is released under the MIT public license : http://www.opensource.org/licenses/MIT.