URL ROUTING

Last updated: June 29th 2026

Router class

Simply-PHP uses Rewrite engine to modify URL's apperance, Then router class is processing URL endpoint and decomposing it into parameters to determine the controller or action to receive the request.

Application Routes

Application routes are located in app/Routes.php

Basic Routing

Basic routing accepts two parameters:

  • URL
  • controllers, action & namespace
Router Code Example

Router::set('home',[
  'controller' => 'home',
  'action'     => 'index'
]);                                      
 

Router above can be convert to much shorter code. Thi can achieve by passing a variable to the router first parameter.

Router Shorter Code Example

Router::set('{controller:home}/{action:index}');                                      
   

Here we pass a controller variable and action variable.

Routing with variables

Router accepts variables by enclosing them with { }.

Router with variable Code Example

Router::set('{controller:home}/{action:index}');                                      

Here we pass a controller variable and action variable.

This route can only call the index action inside HomeController.

Routing using regex variables

Router can accepts variables with regular expression by specifying variable like {var:regex}.

Router Variable with Regex Code Example

Router::set('users/show/{id:\d+}',[
  'controller' => 'user',
  'action' => 'show'
]);                                  

Here wew pass a variable id with regex \d+

telling the router that this variable only accepts a valid number

Here are some regex for variables that can be use for your application

  • [a-z0-9]+(?:-[a-z0-9]+)*$ Valid Slug eg: news-report-2019
  • \w+ accepts letters and numbers
  • \d+ accepts only numbers
  • \bSHOW|\bEDIT|\bUPDATE accepts show, edit and update (case insensitive).
  • You can set your own regex.

Predefined Routes

Simply-PHP has a predefined routes

The Route Resource

Simply has a route resource that address CRUD in a controller. Instead of declaring mutltiple route for CRUD in a controller just declare a route resource for your controller.

Route Resource Code Example

Router::resource('products','ProductController');                      
  

Actions Handle by Route Resource

# URL Description controller@action
1 /products Show product list ProductController@index
2 /products/create Show product create form ProductController@create
3 /products/store Accepts and store data from product create form ProductController@store
4 /products/show/{id} Show single product entry ProductController@show(Request $request)
5 /products/edit/{id} Show product edit form ProductController@edit(Request $request)
6 /products/update/{id} Accepts and update existing data from product create form ProductController@update(Request $request)
7 /products/destroy/{id} Delete the product from the table ProductController@destroy(Request $request)

NOTE: The product controller here only serves as example. You can create your own

Dynamic Routes

Routes can be dynamic by using the advantage of the routes variable

Dynamic Route Code Example

Router::set('dashboard/{action}',[
  'controller' => 'dashboard'
]);                   
    

Notice the action variable here. Router can simply find the action from URL in the controller to be called if it is existing

Optional Parameters

Router class can also accept optional parameters.

Dynamic Route Code Example

Router::set('product/{id?}',[
    'controller' => 'ProductController',
    'action' => 'someAction'
]);                  
  

Route Optional Parameters: (just add ? to the route variable eg: {id?}

The :all? route

Router class can accept any url using the :all? parameter. This can be usefull when you're create a Single Page Application, or If you are using a Front-End framework like React.js, Vue.js, Angular.js, etc.

:all? Route Code Example

Router::set('application/{:all?}',[
    'controller' => 'SinglePageController',
    'action' => 'index'
]);                
  

all parameters pass to http:://myDomain.com/application/you/can/put/anything/here will pass the request to SinglePageController

Route Groups

Routes can be grouped under a common prefix using Router::group(). Groups can be nested.

Route Group Code Example

Router::group('admin', function () {
    Router::get('users', ['controller' => 'AdminController', 'action' => 'users']);
    Router::get('settings', ['controller' => 'AdminController', 'action' => 'settings']);
});
// Matches: /admin/users, /admin/settings
  

HTTP Method Filters

Routes can be restricted to specific HTTP methods using the dedicated helpers.

HTTP Method Route Code Example

Router::get('posts', ['controller' => 'PostController', 'action' => 'index']);
Router::post('posts/store', ['controller' => 'PostController', 'action' => 'store']);
Router::put('posts/update/{id}', ['controller' => 'PostController', 'action' => 'update']);
Router::delete('posts/destroy/{id}', ['controller' => 'PostController', 'action' => 'destroy']);
  

Action Suffix Convention

If a controller action method ends with Action (e.g. indexAction), the router calls it automatically when the route specifies 'action' => 'index'.

Action Suffix Code Example

// Controller method:
class ProductController extends Controller
{
    public function indexAction()
    {
        return view('product.index');
    }
}

// Route - the 'Action' suffix is resolved automatically:
Router::set('products', [
    'controller' => 'ProductController',
    'action' => 'index'
]);
  

Trailing Slash Handling

The router normalizes trailing slashes. /products and /products/ resolve to the same route.

Trailing Slash Code Example

// Both of these match the same route:
Router::set('products', ['controller' => 'ProductController', 'action' => 'index']);
// http://example.com/products
// http://example.com/products/
  

Controller Method Arguments

Controller methods receive their arguments automatically via the ControllerDispatcher. Arguments are resolved by type-hint and parameter name matching.

Request Injection

Type-hint Request to receive the current HTTP request. The same Request instance is shared across all controllers in a request cycle.


use Simple\Request;

class ProductController extends Controller
{
    public function index(Request $request)
    {
        $search = $request->get('search');
        return view('product.index');
    }
}
                                

Route Parameters

Route placeholders are passed as arguments matching the parameter name:


// Route:
Router::set('product/{id}/{slug}', [
    'controller' => 'ProductController',
    'action'     => 'show'
]);

// Controller — parameter names match route placeholders:
class ProductController extends Controller
{
    public function show(string $id, string $slug)
    {
        return "Product #{$id}: {$slug}";
    }
}
                                

Default Values

Parameters with defaults are preserved when the route doesn't provide a value:


class ProductController extends Controller
{
    public function index(string $sort = 'name', int $page = 1)
    {
        // $sort defaults to 'name', $page defaults to 1
    }
}
                                

Auto-wiring (Dependency Injection)

Non-built-in type-hints are recursively resolved. The dispatcher inspects each class's constructor and resolves its dependencies automatically:


class ProductService
{
    public function __construct(private Logger $logger) {}

    public function all(): array { /* ... */ }
}

class ProductController extends Controller
{
    // ProductService and its Logger dependency are auto-resolved:
    public function index(ProductService $service)
    {
        $products = $service->all();
        return view('product.index', ['products' => $products]);
    }
}
                                

Routing with namespace

Sometimes you want to organize your controllers inside their own folder. The folder name then will be the namespace

Example:

You have your controller inside the Admin folder in the Controller's folder.

Your controller's namespace should be: namespace App\Controllers\Admin;

Make sure you are extending the default Controller::class. eg: class AdminController extends \App\Controllers\Controller {

Example of a AdminController inside a Admin folder:

Controller inside another namespace

  <?php
  namespace App\Controllers\Admin;
  
  use App\Controllers\Controller;
  use Simple\Request;
  
  class AdminController extends Controller
  {
  
      public function index()
      {
          return 'hello';
      }
  
  }
                                                     
    
Namespace Route Code Example

The namespace parameter tells the router where to look for the controller:


Router::set('administrator/{action}',[
  'namespace' => 'admin',
  'controller' => 'AdminController'
]);                 
  

Here we pass an admin namespace parameter

Full FQCN Route Code Example

You can also pass the full controller class name directly. When the controller value contains a backslash (\), the router uses it as-is — no namespace prefix or Controller suffix is added:


Router::set('administrator/{action}',[
  'controller' => 'App\Controllers\Admin\AdminController'
]);
  

Both approaches are equivalent. Use whichever is clearer for your project.