Building a Simple PHP Router: A Laravel-Inspired Approach

Jan 18, 2024
laravel
php
webdev

The urge to build a framework I believe is what all PHP developers have an in common. I would be going through some recreating some Laravel/Symphony architecture and for this particular article we are going to be building a Simple Router without autoloading.

We will explore the process of constructing a basic PHP router. We will begin by examining the concept of a router and understanding how it works.

What's is a router?

A router typically refers to a component or piece of code that helps handle the routing of incoming HTTP requests to the appropriate handlers or controllers. Routing is a fundamental concept in web development that involves mapping URLs to specific actions or functions in your application.

In a web application, the router is responsible for examining the incoming request's URL and determining which part of your code should handle that request. This involves parsing the URL, extracting relevant information, and then dispatching the request to the appropriate controller or handle.

How is it going to work?

Every incoming request to your server that doesn't include an extension automatically is loaded as part of your index.php file. You can as where include a .htaccess file (for apache) or edit your configuration (nginx) to load all requests whether with or without extension through the index.php but I would be sticking to just redirecting url without extensions.

After the long sermon, Let's Start ⚡️

We are going to creating 5 files and 2 folder.

  1. `app` folder

  2. `index.php` file

  3. `router.php` file inside the `app` folder/directory

  4. `web.php` file for our routes

  5. `check_route.php` file inside the `app` folder/directory

We would be having the 5 HTTP Methods (GET, POST, PUT, PATCH) and our Route should appear like

Router::get('/login',function(){

    /* run some codes */
    echo "hello world!";
});

index.php

We would be needing less than 3 lines of code in the page as its nothing be a reference to the files we need to load at each request sent to our server.

First we create the file and we require the check_route file.

<?php
    
require_once __DIR__ ."/check_route.php";

router.php

This is the file that would contain the router class and the 5 Methods (functions). We initialise our class with the required static functions

Bear in mind, a static function is a function that is associated with a class rather than with an instance of the class. This means that we can call these static functions on the class itself, rather than on an object created from that class.

so instead of

$router = new Router();

We call it as

Router::get(/* params */);

Back to creating our class. First we need all our routes stored in an array

<?php

class Router{

private static $routes = [];

We would need a static function that retrieve all the routes in this array anytime we call it

public static function getRoutes() {

    return self::$routes;

}

Then we add our 5 HTTP methods as static functions

<?php

class Router{

    /** Array of routes */
    private static $routes = [];
        
    /** Retrieves our routes */

    public static function getRoutes() {

        return self::$routes;
    }


    public static function get(){
         /* The get http method */
    }

    public static function post(){
         /* The post http method */
    }

    public static function patch(){
        /* The patch http method */
    }

    public static function put(){
        /* The put http method */
    }

    public static function delete(){
        /* The delete http method */
    }
}

Currently, we have the five required static functions; however they're not populated yet with the required parameters and code. Let do this using the `public static function get()`

public static function get($uri, $handler) {

    self::$routes[] = [
        'method' => 'GET',
        'uri' => $uri,
        'handler' => $handler,
    ];
}

From this code we can see the get function requiring 2 arguments:

  • The $uri

  • The $handler. In this case the $handler typically refers to the code or function that gets executed when a particular route is matched. This handler is responsible for processing the incoming request, performing any necessary logic and responding.

We assign the route as an array to the private $route array. The method is basically the HTTP METHOD being called, we can duplicate this code into other functions and update their Method.

We're done with the Router.php

web.php

This is file we list our routes

<?php
require_once __DIR__ .'/app/router.php';


Router::get('/hello-world', function(){
    
    echo "Hello World!";
});

We required the router.php before being about to use the static get function. When we're done we should be able to visit locahost/hello-world and it would echo hello world

check_route.php

In this file we would attempt to match the current HTTP request's method and URI against our array of predefined from the web.php and router.php. If a matching route is found, we calls the associated handler function or closure. If no matching route is found, we returns a "Not Found" response with a 404 HTTP status code

First we include the web.php file then retrieve the registered routes using Router::getRoutes() and also initialise a variable $route_exists. This variable will be used to track whether a matching route has been found and the default value would be force

<?php 

require_once __DIR__ ."/../web.php";

$routes = Router::getRoutes();
$route_exists = false;

Then we loop through the routes and attempt to find a matching HTTP request's method and URI for the current route's method and URI, if exists we would check if it's callable. If callable, we executes the handler function using `call_user_func()` and sets the `$route_exists` variable to `true` to indicate that a matching route has been found.

foreach ($routes as $route) {
    if($route['method'] === $_SERVER['REQUEST_METHOD'] && $route['uri'] === $_SERVER['REQUEST_URI']){

        $handler = $route['handler'];

        if (is_callable($handler)){
            call_user_func($handler);
        }

        $route_exists = true;
    }
    
}

What if the current route's method and URI doesn't match any predefined route?

if(!$route_exists){
    http_response_code(404);

    echo"Not found";
}

We sets a 404 HTTP response code and echo a "Not Found" message. Serve your project and visit /hello-world url.

Conclusion

In this article we have been able to define router and show an easy around getting it up and ready. For more extensive router with the

Router::get('/hello-world', function(){
    
    echo 'Hello World!';
});

and with parameters , be ready to checkout my package 😉

© 2023 Ogbonna Henry. All rights reserved.