Simple and powerful php router
Switch branches/tags
Clone or download

readme.md

JetFire PHP Routing

SensioLabsInsight Build Status Scrutinizer Code Quality

A simple & powerful router for PHP 5.4+

Features

V1.3

V1.2

V1.1

V1.0

Getting started

  1. PHP 5.4+ is required
  2. Install JetFire\Routing using Composer
  3. Setup URL rewriting so that all requests are handled by index.php

Installation

Via composer

$ composer require jetfirephp/routing

.htaccess

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.[a-zA-Z0-9\-\_\/]*)$ index.php?url=$1 [QSA,L]

Usage

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();

Matcher

JetFire\Routing provide 2 type of matcher for your routes : JetFire\Routing\Matcher\ArrayMatcher and JetFire\Routing\Matcher\UriMatcher

Uri Matcher

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

Resolver

Here are the list of Uri Matcher resolver :

$resolver = [
    'isControllerAndTemplate',
    'isController',
    'isTemplate'
];
Template resolver

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'],

]);
Controller resolver

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
    }
}

Array Matcher

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_'
];
Resolver

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.

Template resolver
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-]*'],
	],

];

Controller resolver
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-]*'],
	],
	
];

Controller and Template resolver
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-]*'],
    ],

];

Controller group resolver
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}',
	],

];

Closure resolver
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-]*'],
	],
	
];

Closure and Template resolver
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-]*'],
    ],
    
];

Block Routes

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();

Router Configuration

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,
]);

Collection Options

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'
];

Named Routes

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.

REST Routing

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'] 
	],
];

Prefix

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']);

Middleware

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.

Custom Matcher and Dispatcher

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();
}

Integration with other libraries

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
	// ...
]);

Subdomain

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'
];

API

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

License

The JetFire Routing is released under the MIT public license : http://www.opensource.org/licenses/MIT.