agashe / sigmaphp-router
PHP Router
Requires (Dev)
- phpunit/phpunit: ^9.5
This package is auto-updated.
Last update: 2025-03-23 09:53:27 UTC
README
A fast and simple router for PHP , you can use for your projects to provide user friendly URLs , you can build your app in a simple functional style , to write RESTfull API services or to build a fully functional MVC.
Features
- Support placeholder parameters e.g
{name}
- Use optional parameters with the route
- Support all HTTP methods GET, POST, PUT ... etc
- Routes can accept multiple HTTP methods
- Support
any
method , so the route can accept all HTTP methods - Routes Validation using regex expressions
- Actions can be implemented as regular functions or controller's method
- Support for Single Action Controllers
- Middlewares , that can be run before your route
- Route Groups which support middlewares and prefix
- URL generation using the route name
- Default page not found (404) handler and you can define custom handler
- Custom action runners , for advanced customization
Installation
composer require agashe/sigmaphp-router
Configurations
Depending on your server , you will need to use one of the provided server configs , including with the router config files for 4 types of servers : Apache , Nginx , Lighttpd and IIS.
To use the router with any of these servers , just copy the corresponding config file from the configs
folder to the root folder of your project.
For example , in case you are using Apache server , copy the apache_htaccess
file and rename it to the proper name .htaccess
Please note : all of the provided config files , assume that the main entry point to your application is index.php
located in the root path of your project's directory. If you have different setup for your project , check please the config and make sure it's pointing to the correct path.
So if the index.php
is located under the public
folder.
Then in the .htaccess
file , change index.php
to public/index.php
, and the same goes for other servers.
Documentation
Table of Contents
- Basic Setup
- Base Path
- HTTP Methods
- Parameters
- Validation
- Actions
- Middlewares
- Route Groups
- Page not found handling
- URL Generation
- Action Runners
Basic Setup
In order to start using SigmaPHP-Router in your app , in the main entry point of your app (say for example index.php
), you first need define the routes array , then pass that array to the constructor and finally call the the run()
method.
<?php
require 'vendor/autoload.php';
use SigmaPHP\Router\Router;
$routes = [
[
'name' => 'users.profile',
'path' => '/users/profile',
'method' => 'get',
'controller' => UserController::class,
'action' => 'profile',
],
];
// initialize the router
$router = new Router($routes);
// fire the router
$router->run();
Alternatively you can save your routes in a separated file and load that file in your app. Even better you can have multiple route files each serves a different purpose.
web.php
<?php
return [
[
'name' => 'users.profile',
'path' => '/users/profile',
'method' => 'get',
'controller' => UserController::class,
'action' => 'profile',
],
];
api.php
<?php
return [
[
'name' => 'api.users.profile',
'path' => '/api/v1/users/profile',
'method' => 'get',
'controller' => UserApiController::class,
'action' => 'profileJson',
],
];
and finally in index.php
:
<?php
require 'vendor/autoload.php';
use SigmaPHP\Router\Router;
$webRoutes = require('web.php');
$apiRoutes = require('api.php');
// initialize the router
$router = new Router(array_merge($webRoutes, $apiRoutes));
// fire the router
$router->run();
To define a new route , you simply append an route array to the routes array , a valid route should at least have a path and an action :
$routes = [
[
'path' => '/users/profile',
'action' => 'get_user_profile',
],
];
The route's name is optional , put it is recommended to give your routes a name , so it becomes easier to work with them , like generating URL using the route's name.
Base Path
In case your application exists in sub-folder of your domain for example http://localhost/my-app
, you can set the root path in the router
constructor , using the second parameter:
<?php
require 'vendor/autoload.php';
use SigmaPHP\Router\Router;
$routes = [
[
'name' => 'users.profile',
'path' => '/users/profile',
'method' => 'get',
'controller' => UserController::class,
'action' => 'profile',
],
];
// define app base path
const BASE_PATH = '/my-app';
// initialize the router
$router = new Router($routes, BASE_PATH);
// fire the router
$router->run();
HTTP Methods
SigmaPHP-Router support all HTTP methods GET, POST, PUT, DELETE ... etc , a single route can support one or more HTTP methods :
$routes = [
[
'name' => 'users.profile',
'path' => '/users/profile',
'method' => 'get,post',
'controller' => UserController::class,
'action' => 'profile',
],
];
Also SigmaPHP-Router provides a special method type any
, which allows the route to accept all of the HTTP methods :
$routes = [
[
'name' => 'users.profile',
'path' => '/users/profile',
'method' => 'any',
'controller' => UserController::class,
'action' => 'profile',
],
];
If no HTTP was provided , then the HTP method for the route will be automatically set to GET.
Parameters
Route parameters follow the placeholder style (like Laravel) :
$routes = [
[
'name' => 'admin.users.address',
'path' => '/admin/users/{user_id}/addresses/{address_id}',
'method' => 'get',
'controller' => AdminPanelUserController::class,
'action' => 'getUserAddressDetails',
],
];
..... In AdminPanelUserController.php
public function getUserAddressDetails($userId, $addressId) {
...
}
Also optional parameters can be used by adding ?
to the parameter , but this option can only be used with last parameter of your route :
$routes = [
[
'name' => 'products.list',
'path' => '/products/{id?}',
'method' => 'get',
'controller' => ProductController::class,
'action' => 'list',
],
];
So the id
in this route can be omitted , so calling /products
or /products/15
are fine.
and lastly don't forget to handle the optional parameter in your action , by adding a default value for the action:
..... In ProductController.php
public function list($id = null) {
...
}
Validation
A validation rules can be added to your routes , in form of regular expressions :
$routes = [
[
'name' => 'orders.show',
'path' => '/orders/details/{order_id}',
'method' => 'get',
'controller' => OrderController::class,
'action' => 'show',
'validation' => [
'order_id' => '[0-9]+'
]
],
];
In the example above the router will match only order_id
that only contains digits , so something like /orders/details/abcd
won't be matched and the router will return 404 - page not found.
Actions
In SigmaPHP-Router an action is the handler which will be executed to process the route functionality.
Actions are divided into 2 categories , first controller based , which simply are classes that contain multiple methods , usually the controller responsible for handling tasks in which grouped by the same model or functionality like PostController
or UserLoginController
.
As we saw in the previous examples , we need to pass the controller name and the method name :
$routes = [
[
'name' => 'orders.show',
'path' => '/orders/details/{order_id}',
'method' => 'get',
'controller' => OrderController::class,
'action' => 'show',
],
];
We use the special constant ::class
in order to get the full name including the namespace of the controller. you can instead write the full path if you prefer , for example :
$routes = [
[
'name' => 'orders.show',
'path' => '/orders/details/{order_id}',
'method' => 'get',
'controller' => App\Web\Controllers\OrderController,
'action' => 'show',
],
];
The second type of actions are functions based , and in this case you just add the function name without controller , and the router simply will call that function , so for example :
$routes = [
[
'name' => 'about_page',
'path' => '/about',
'method' => 'get',
'action' => 'create_about_page',
],
];
.... somewhere in your application define the function and call it either in the same index.php or another file
// pages.php
<?php
function create_about_page() {
print "About Us";
}
Finally SigmaPHP-Router also has support for Single Action Controllers , so no need to pass action name ,
and the router will automatically search for the PHP magic method __invoke()
to run :
$routes = [
[
'name' => 'notification.send_email',
'path' => '/notification/send-email',
'method' => 'post',
'controller' => SendEmailController::class,
],
];
And in the SendEmailController :
// SendEmailController.php
<?php
class SendEmailController
{
public function __invoke()
{
// .... some code to send email
}
}
Middlewares
Usually in any application you will need to run some checks before allowing the user to perform the action , like for example check if is he logged in , has the proper permissions , and so on.
So out of the box SigmaPHP-Router provides the ability to call middlewares before the execution of your route's action.
$routes = [
[
'name' => 'orders.create',
'path' => '/orders',
'method' => 'post',
'middlewares' => [
[AuthMiddleware::class, 'handler'],
[UserCanCreateOrderMiddleware::class, 'check'],
],
'controller' => OrderController::class,
'action' => 'create'
],
];
In case of class based middlewares , the router will require the middleware class name and the name of the method that will be executed.
In addition the middlewares could be written as regular functions , and in this case we pass an array of functions name :
$routes = [
[
'name' => 'orders.create',
'path' => '/orders',
'method' => 'post',
'middlewares' => ['is_user_auth', 'check_user_permissions'],
'controller' => OrderController::class,
'action' => 'create'
],
];
Creating the middleware classes/functions is completely depending on your application , so in your middleware you could have something similar to :
<?php
class AuthMiddleware
{
public function handler()
{
session_start();
if (empty($_SESSION['user])) {
header('Location: http://example.com');
exit();
}
}
}
Route Groups
Group routes is an essential feature for any router , so you can apply certain prefix or middleware to a group of routes.
To create a new route group , use the following schema :
$routes = [
[
'group' => 'api',
'prefix' => '/api/v1/',
'middlewares' => [
[AuthMiddleware::class, 'handler'],
[UserAccountIsActiveMiddleware::class, 'handler'],
],
'routes' => [
[
'name' => 'users.profile',
'path' => '/users/profile',
'method' => 'get',
'middlewares' => [
[CheckIfUserCanEditProfileMiddleware::class, 'handler'],
],
'controller' => UserApiController::class,
'action' => 'profile'
],
]
],
];
The only items required for a group is the group name and the routes array. The name will be added to all of its routes , so in the example above , the final route name will be : api.users.profile
and the route path : /api/v1/users/profile
Both prefix
and middlewares
are optional , a routes group could either has prefix , middlewares , both or non of them.
For the routes definition , nothing changed all features are implemented as regular routes.
Please Note : SigmaPHP-Router doesn't support sub-groups so you can't define a routes group inside another routes group !
Page not found handling
By default in case the requested URI didn't match , the router will return 404 HTTP status code , with simple message 404 , The Requested URL Was Not Found
You change this default behavior by passing a custom handler name as an argument to the method setPageNotFoundHandler
<?php
require 'vendor/autoload.php';
use SigmaPHP\Router\Router;
$routes = [
[
'name' => 'users.profile',
'path' => '/users/profile',
'method' => 'get',
'controller' => UserController::class,
'action' => 'profile',
],
];
// initialize the router
$router = new Router($routes);
// set custom 404 (Page Not Found) handler
$router->setPageNotFoundHandler('my_custom_404_handler');
// fire the router
$router->run();
.... and somewhere in your code , you define that function :
function my_custom_404_handler() {
http_response_code(404);
echo "<h1>My custom message</h1>";
exit();
}
So now you can add your custom 404 message , page design or redirect the user another route.
And as usual you can instead of using function handler , you can use a class , so you pass class name and the method name :
// set custom 404 (Page Not Found) handler
$router->setPageNotFoundHandler([
MyCustomPageNotFoundHandler::class, 'handle'
]);
URL Generation
In a lot of cases , you will need a way to create an URL for your models , for example a link to show order details , SigmaPHP-Router provides a method called url
, which accept a route name and parameters array. and generate the URL , let's see an example :
$routes = [
[
'name' => 'order.items.details',
'path' => '/order/{order_id}/items/{item_id?}',
'method' => 'get',
'controller' => OrderController::class,
'action' => 'getOrderItems',
],
];
So to generate an URL for the route above :
$orderId = 5;
$itemId = 10;
$itemsURL = $router->url('order.items.details', [
'order_id' => $orderId,
'item_id' => $itemId,
]);
..... if we print $itemURL :
http://localhost/order/5/items/10
The router will automatically add the host and check if the https is enabled.
Also in case your route doesn't require parameters , or accept optional parameters , you can skip the second parameter.
$routes = [
[
'name' => 'print_terms_of_usage',
'path' => '/terms-of-usage',
'method' => 'get',
'action' => 'print_terms_of_usage',
],
];
$pageURL = $router->url('print_terms_of_usage');
..... if we print $pageURL :
http://localhost/terms-of-usage
Action Runners
Action runner is the execution unit inside SigmaPHP-Router , it's the last step in the route execution cycle , starting from matching to applying middlewares and other validations , we finally reach to the execution phase , in this phase the runner will take care of executing the action either by calling the action function or a method defined in class.
But in some cases , writing our custom runner might be a necessity , for example if we want to preform some logic before the execution start , for example add some logs before or after the execution or handle dependency injection.
By default SigmaPHP-Router ships with the DefaultRunner
, to register your own runner , all what you have to do is to define and new class that implements the RunnerInterface
:
<?php
namespace SigmaPHP\Router\Interfaces;
/**
* Runner Interface
*/
interface RunnerInterface
{
/**
* Execute the route's action.
*
* @param array $route
* @return void
*/
public function execute($route);
}
A simple interface that only has one method execute
which only accepts the route as a parameter.
<?php
namespace MyApp\Util;
use SigmaPHP\Router\Interfaces\RunnerInterface;
class MyCustomRunner implements RunnerInterface
{
/**
* Execute the route's action.
*
* @param array $route
* @return void
*/
public function execute($route)
{
// here we define our execution logic , save logs
// connect to DB , inject dependencies or use call_user_func
// to call actions ... etc
}
}
Please note , that inside the execute
method we have three main items defined in the route array , that we should use to execute the route :
// first is the controller name and path , which also
// could be empty if we are just calling standalone actions
// example : \MyApp\Controllers\MainController
$route['controller']
// then the action name , which again could belong to a
// controller or just a name of a function defined in our app
$route['action']
// finally the parameters array , which contains all of the
// parameters extracted form the url and this array could be empty
// if no parameters were provided
// example : ['user_id' => 5, 'order_id' => 10]
$route['parameters']
// using these information we could implement our logic like so :
call_user_func($route['action'],...$route['parameters']);
Finally we can register our custom runner using the setActionRunner
method :
// initialize the router
$router = new Router($webRoutes);
// set the runner before starting the router
$router->setActionRunner(MyCustomRunner::class);
// run the router
$router->run();
The setActionRunner
method also has one optional parameter
called parameters
which of type array and can be used to pass arguments to the runner constructor :
// initialize the router
$router = new Router($webRoutes);
// pass the parameters array
$router->setActionRunner(MyCustomRunner::class, [$service]);
// run the router
$router->run();
And in some cases depending on how complex our project , we can control the runners based some conditions like environment variables ... etc
// initialize the router
$router = new Router($webRoutes);
// check the .env and set the runner
if ($_ENV['environment'] == 'dev') {
$router->setActionRunner(DevRunner::class);
}
else if ($_ENV['environment'] == 'prod') {
$router->setActionRunner(ProdRunner::class);
}
else {
$router->setActionRunner(DefaultRunner::class);
}
// run the router
$router->run();
Examples
$routes = [
[
'name' => 'home',
'path' => '/',
'method' => 'get',
'action' => 'home_page',
],
[
'name' => 'contact_us.show',
'path' => '/contact-us',
'method' => 'get',
'controller' => ContactUsController::class,
'action' => 'showContactUsForm'
],
[
'name' => 'contact_us.submit',
'path' => '/contact-us',
'method' => 'post',
'controller' => ContactUsController::class,
'action' => 'submitContactUsForm'
],
[
'group' => 'posts',
'prefix' => 'posts/',
'middlewares' => [
[AuthMiddleware::class, 'handler'],
[UserIsActiveMiddleware::class, 'handler'],
[UserCanControlPostsMiddleware::class, 'handler'],
],
'routes' => [
[
'name' => 'list',
'path' => '/{id?}',
'method' => 'get',
'controller' => PostController::class,
'action' => 'index',
'validation' => [
'id' => '[0-9]+'
]
],
[
'name' => 'create',
'path' => '/create',
'method' => 'get,post',
'controller' => PostController::class,
'action' => 'create'
],
[
'name' => 'update',
'path' => '/update/{id}',
'method' => 'get,patch',
'controller' => PostController::class,
'action' => 'update',
'validation' => [
'id' => '[0-9]+'
]
],
[
'name' => 'delete',
'path' => '/{id}',
'method' => 'delete',
'controller' => PostController::class,
'action' => 'delete',
'validation' => [
'id' => '[0-9]+'
]
'middlewares' => [
[CheckPostIsNotPublishedMiddleware::class, 'handler'],
],
],
]
]
];
License
(SigmaPHP-Router) released under the terms of the MIT license.