Understanding the Basics of Routing

Routing is the mechanism that maps incoming requests to specific code that handles those requests. A well-structured router allows developers to define routes in a clean and organized manner, making it easier to manage application logic.

Key Features of Our Custom Router

  1. Dynamic Routing: Support for dynamic URL segments.
  2. HTTP Method Support: Differentiate between GET, POST, PUT, DELETE, etc.
  3. Middleware Integration: Ability to add middleware for request handling.
  4. Error Handling: Graceful handling of 404 errors.

Building the Router Class

Let's start by creating a basic Router class that will handle our routing logic.

<?php

class Router {
    private $routes = [];

    public function addRoute($method, $path, $callback) {
        $this->routes[] = [
            'method' => $method,
            'path' => $path,
            'callback' => $callback,
        ];
    }

    public function dispatch($method, $uri) {
        foreach ($this->routes as $route) {
            if ($route['method'] === $method && $route['path'] === $uri) {
                return call_user_func($route['callback']);
            }
        }
        http_response_code(404);
        echo "404 Not Found";
    }
}

Adding Routes

Now that we have a basic router class, we can add routes to our application. Here's how to define routes using the addRoute method.

$router = new Router();

$router->addRoute('GET', '/home', function() {
    return "Welcome to the Home Page!";
});

$router->addRoute('GET', '/about', function() {
    return "This is the About Page.";
});

$router->addRoute('POST', '/submit', function() {
    return "Form submitted!";
});

Dynamic Routing

To handle dynamic segments in URLs, we can modify our dispatch method to use regular expressions. This allows us to capture dynamic parameters from the URL.

public function dispatch($method, $uri) {
    foreach ($this->routes as $route) {
        $pattern = preg_replace('/\{(\w+)\}/', '([^/]+)', $route['path']);
        if ($route['method'] === $method && preg_match("#^$pattern$#", $uri, $matches)) {
            array_shift($matches); // Remove the full match
            return call_user_func_array($route['callback'], $matches);
        }
    }
    http_response_code(404);
    echo "404 Not Found";
}

Example of Dynamic Routes

Now, let's add a dynamic route to our router.

$router->addRoute('GET', '/user/{id}', function($id) {
    return "User ID: " . htmlspecialchars($id);
});

// Dispatching a request
$router->dispatch('GET', '/user/123'); // Output: User ID: 123

Middleware Integration

Middleware can be used to perform actions before or after route handling. Here’s a simple implementation of middleware support in our router.

private $middleware = [];

public function addMiddleware(callable $middleware) {
    $this->middleware[] = $middleware;
}

public function dispatch($method, $uri) {
    foreach ($this->middleware as $middleware) {
        call_user_func($middleware);
    }
    // Existing dispatch logic...
}

Example Middleware

You can create middleware for logging or authentication. Here’s an example of a simple logging middleware.

$router->addMiddleware(function() {
    error_log("Request made at: " . date('Y-m-d H:i:s'));
});

Error Handling

In our router, we have already implemented basic error handling for 404 errors. However, you can extend this to include custom error pages or logging.

public function dispatch($method, $uri) {
    // Existing logic...

    http_response_code(404);
    include '404.php'; // Custom 404 page
}

Conclusion

In this tutorial, we have built a custom PHP router that supports dynamic routing, HTTP methods, middleware, and error handling. This router can serve as a foundation for more complex applications, providing a clear structure for managing routes.

Learn more with useful resources