1. php
  2. /web development
  3. /routing

URL Routing

Introduction to URL Routing

URL routing is a fundamental concept in web development that maps URLs to specific code handlers. It enables clean, SEO-friendly URLs and provides a structured way to organize web application endpoints.

Basic Routing Concepts

What is URL Routing?

URL routing is the process of matching incoming URLs to specific actions or controllers in your application. Instead of using query strings or file-based routing, modern routing systems use patterns to determine which code should handle a request.

<?php
// Traditional approach (not ideal)
// http://example.com/index.php?page=user&action=profile&id=123

// Modern routing approach (better)
// http://example.com/user/profile/123
?>

Benefits of URL Routing

  1. Clean URLs: More readable and SEO-friendly
  2. Flexibility: Easy to change URL structure without affecting code
  3. RESTful Design: Supports REST API conventions
  4. Security: Hides internal file structure
  5. Maintainability: Centralized URL management

Simple Router Implementation

Basic Router Class

<?php
class SimpleRouter {
    private $routes = [];
    
    public function addRoute($method, $pattern, $handler) {
        $this->routes[] = [
            'method' => strtoupper($method),
            'pattern' => $pattern,
            'handler' => $handler
        ];
    }
    
    public function get($pattern, $handler) {
        $this->addRoute('GET', $pattern, $handler);
    }
    
    public function post($pattern, $handler) {
        $this->addRoute('POST', $pattern, $handler);
    }
    
    public function put($pattern, $handler) {
        $this->addRoute('PUT', $pattern, $handler);
    }
    
    public function delete($pattern, $handler) {
        $this->addRoute('DELETE', $pattern, $handler);
    }
    
    public function dispatch($requestUri, $requestMethod) {
        foreach ($this->routes as $route) {
            if ($route['method'] !== strtoupper($requestMethod)) {
                continue;
            }
            
            $pattern = '#^' . $route['pattern'] . '$#';
            if (preg_match($pattern, $requestUri, $matches)) {
                array_shift($matches); // Remove full match
                return $this->callHandler($route['handler'], $matches);
            }
        }
        
        $this->handle404();
    }
    
    private function callHandler($handler, $params) {
        if (is_callable($handler)) {
            return call_user_func_array($handler, $params);
        }
        
        if (is_string($handler)) {
            list($controller, $method) = explode('@', $handler);
            if (class_exists($controller)) {
                $instance = new $controller();
                if (method_exists($instance, $method)) {
                    return call_user_func_array([$instance, $method], $params);
                }
            }
        }
        
        throw new Exception("Handler not found: " . print_r($handler, true));
    }
    
    private function handle404() {
        http_response_code(404);
        echo "404 - Page Not Found";
    }
}

// Usage example
$router = new SimpleRouter();

$router->get('/', function() {
    echo "Welcome to the home page!";
});

$router->get('/user/(\d+)', function($id) {
    echo "User profile for ID: " . $id;
});

$router->get('/user/([a-zA-Z0-9_]+)', function($username) {
    echo "User profile for: " . $username;
});

// Dispatch the current request
$requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$requestMethod = $_SERVER['REQUEST_METHOD'];
$router->dispatch($requestUri, $requestMethod);
?>

Advanced Routing Features

Named Parameters

<?php
class AdvancedRouter {
    private $routes = [];
    private $namedRoutes = [];
    
    public function addRoute($method, $pattern, $handler, $name = null) {
        $compiledPattern = $this->compilePattern($pattern);
        
        $route = [
            'method' => strtoupper($method),
            'pattern' => $pattern,
            'compiled' => $compiledPattern['pattern'],
            'params' => $compiledPattern['params'],
            'handler' => $handler
        ];
        
        $this->routes[] = $route;
        
        if ($name) {
            $this->namedRoutes[$name] = $route;
        }
    }
    
    private function compilePattern($pattern) {
        $params = [];
        
        // Replace named parameters {param} with regex
        $compiled = preg_replace_callback(
            '/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/',
            function($matches) use (&$params) {
                $params[] = $matches[1];
                return '([^/]+)';
            },
            $pattern
        );
        
        // Replace optional parameters {param?}
        $compiled = preg_replace_callback(
            '/\{([a-zA-Z_][a-zA-Z0-9_]*)\?\}/',
            function($matches) use (&$params) {
                $params[] = $matches[1];
                return '([^/]*)';
            },
            $compiled
        );
        
        return [
            'pattern' => '#^' . $compiled . '$#',
            'params' => $params
        ];
    }
    
    public function dispatch($requestUri, $requestMethod) {
        foreach ($this->routes as $route) {
            if ($route['method'] !== strtoupper($requestMethod)) {
                continue;
            }
            
            if (preg_match($route['compiled'], $requestUri, $matches)) {
                array_shift($matches); // Remove full match
                
                // Create associative array of parameters
                $params = [];
                foreach ($route['params'] as $index => $name) {
                    $params[$name] = $matches[$index] ?? null;
                }
                
                return $this->callHandler($route['handler'], $params);
            }
        }
        
        $this->handle404();
    }
    
    public function url($name, $params = []) {
        if (!isset($this->namedRoutes[$name])) {
            throw new Exception("Named route '{$name}' not found");
        }
        
        $pattern = $this->namedRoutes[$name]['pattern'];
        
        foreach ($params as $key => $value) {
            $pattern = str_replace('{' . $key . '}', $value, $pattern);
            $pattern = str_replace('{' . $key . '?}', $value, $pattern);
        }
        
        // Remove unused optional parameters
        $pattern = preg_replace('/\{[^}]+\?\}/', '', $pattern);
        
        return $pattern;
    }
    
    private function callHandler($handler, $params) {
        if (is_callable($handler)) {
            return call_user_func($handler, $params);
        }
        
        if (is_string($handler)) {
            list($controller, $method) = explode('@', $handler);
            if (class_exists($controller)) {
                $instance = new $controller();
                if (method_exists($instance, $method)) {
                    return call_user_func([$instance, $method], $params);
                }
            }
        }
        
        throw new Exception("Handler not found");
    }
    
    private function handle404() {
        http_response_code(404);
        echo "404 - Page Not Found";
    }
}

// Usage with named parameters
$router = new AdvancedRouter();

$router->addRoute('GET', '/user/{id}', function($params) {
    echo "User ID: " . $params['id'];
}, 'user.show');

$router->addRoute('GET', '/user/{id}/post/{slug?}', function($params) {
    echo "User: " . $params['id'] . ", Post: " . ($params['slug'] ?? 'all posts');
}, 'user.posts');

// Generate URLs
echo $router->url('user.show', ['id' => 123]); // /user/123
echo $router->url('user.posts', ['id' => 123, 'slug' => 'hello-world']); // /user/123/post/hello-world
?>

Route Groups and Middleware

<?php
class RouterWithMiddleware {
    private $routes = [];
    private $middleware = [];
    private $groupStack = [];
    
    public function group($prefix, $callback, $middleware = []) {
        $this->groupStack[] = [
            'prefix' => $prefix,
            'middleware' => $middleware
        ];
        
        call_user_func($callback, $this);
        
        array_pop($this->groupStack);
    }
    
    public function get($pattern, $handler, $middleware = []) {
        $this->addRoute('GET', $pattern, $handler, $middleware);
    }
    
    public function post($pattern, $handler, $middleware = []) {
        $this->addRoute('POST', $pattern, $handler, $middleware);
    }
    
    private function addRoute($method, $pattern, $handler, $middleware = []) {
        $fullPattern = $this->buildFullPattern($pattern);
        $fullMiddleware = $this->buildFullMiddleware($middleware);
        
        $this->routes[] = [
            'method' => $method,
            'pattern' => $fullPattern,
            'handler' => $handler,
            'middleware' => $fullMiddleware
        ];
    }
    
    private function buildFullPattern($pattern) {
        $prefix = '';
        
        foreach ($this->groupStack as $group) {
            $prefix .= $group['prefix'];
        }
        
        return $prefix . $pattern;
    }
    
    private function buildFullMiddleware($middleware) {
        $fullMiddleware = [];
        
        foreach ($this->groupStack as $group) {
            $fullMiddleware = array_merge($fullMiddleware, $group['middleware']);
        }
        
        return array_merge($fullMiddleware, $middleware);
    }
    
    public function dispatch($requestUri, $requestMethod) {
        foreach ($this->routes as $route) {
            if ($route['method'] !== $requestMethod) {
                continue;
            }
            
            $pattern = '#^' . $route['pattern'] . '$#';
            if (preg_match($pattern, $requestUri, $matches)) {
                array_shift($matches);
                
                // Execute middleware
                foreach ($route['middleware'] as $middlewareClass) {
                    $middleware = new $middlewareClass();
                    if (!$middleware->handle()) {
                        return; // Middleware blocked the request
                    }
                }
                
                return $this->callHandler($route['handler'], $matches);
            }
        }
        
        $this->handle404();
    }
    
    private function callHandler($handler, $params) {
        if (is_callable($handler)) {
            return call_user_func_array($handler, $params);
        }
        
        if (is_string($handler)) {
            list($controller, $method) = explode('@', $handler);
            if (class_exists($controller)) {
                $instance = new $controller();
                if (method_exists($instance, $method)) {
                    return call_user_func_array([$instance, $method], $params);
                }
            }
        }
        
        throw new Exception("Handler not found");
    }
    
    private function handle404() {
        http_response_code(404);
        echo "404 - Page Not Found";
    }
}

// Middleware examples
class AuthMiddleware {
    public function handle() {
        if (!isset($_SESSION['user_id'])) {
            http_response_code(401);
            echo "Unauthorized";
            return false;
        }
        return true;
    }
}

class AdminMiddleware {
    public function handle() {
        if (!isset($_SESSION['is_admin']) || !$_SESSION['is_admin']) {
            http_response_code(403);
            echo "Forbidden";
            return false;
        }
        return true;
    }
}

// Usage with groups and middleware
$router = new RouterWithMiddleware();

// Public routes
$router->get('/', function() {
    echo "Home page";
});

$router->get('/login', function() {
    echo "Login page";
});

// Protected routes
$router->group('/user', function($router) {
    $router->get('/profile', function() {
        echo "User profile";
    });
    
    $router->get('/settings', function() {
        echo "User settings";
    });
}, ['AuthMiddleware']);

// Admin routes
$router->group('/admin', function($router) {
    $router->get('/dashboard', function() {
        echo "Admin dashboard";
    });
    
    $router->get('/users', function() {
        echo "Manage users";
    });
}, ['AuthMiddleware', 'AdminMiddleware']);
?>

RESTful Routing

REST Controller Pattern

<?php
class RestRouter {
    private $router;
    
    public function __construct($router) {
        $this->router = $router;
    }
    
    public function resource($name, $controller) {
        $singular = rtrim($name, 's');
        
        // GET /users - index
        $this->router->get("/{$name}", "{$controller}@index");
        
        // GET /users/create - show create form
        $this->router->get("/{$name}/create", "{$controller}@create");
        
        // POST /users - store new resource
        $this->router->post("/{$name}", "{$controller}@store");
        
        // GET /users/{id} - show specific resource
        $this->router->get("/{$name}/(\d+)", "{$controller}@show");
        
        // GET /users/{id}/edit - show edit form
        $this->router->get("/{$name}/(\d+)/edit", "{$controller}@edit");
        
        // PUT /users/{id} - update specific resource
        $this->router->put("/{$name}/(\d+)", "{$controller}@update");
        
        // DELETE /users/{id} - delete specific resource
        $this->router->delete("/{$name}/(\d+)", "{$controller}@destroy");
    }
    
    public function apiResource($name, $controller) {
        // API routes (no create/edit forms)
        $this->router->get("/{$name}", "{$controller}@index");
        $this->router->post("/{$name}", "{$controller}@store");
        $this->router->get("/{$name}/(\d+)", "{$controller}@show");
        $this->router->put("/{$name}/(\d+)", "{$controller}@update");
        $this->router->delete("/{$name}/(\d+)", "{$controller}@destroy");
    }
}

// Example controller following REST conventions
class UserController {
    public function index() {
        // List all users
        echo "All users";
    }
    
    public function create() {
        // Show form to create new user
        echo "Create user form";
    }
    
    public function store() {
        // Store new user from form data
        echo "Store new user";
    }
    
    public function show($id) {
        // Show specific user
        echo "Show user {$id}";
    }
    
    public function edit($id) {
        // Show form to edit user
        echo "Edit user {$id} form";
    }
    
    public function update($id) {
        // Update specific user
        echo "Update user {$id}";
    }
    
    public function destroy($id) {
        // Delete specific user
        echo "Delete user {$id}";
    }
}

// Usage
$router = new SimpleRouter();
$restRouter = new RestRouter($router);

$restRouter->resource('users', 'UserController');
$restRouter->apiResource('api/users', 'ApiUserController');
?>

URL Generation and Helpers

URL Helper Functions

<?php
class UrlHelper {
    private static $baseUrl;
    private static $router;
    
    public static function setBaseUrl($url) {
        self::$baseUrl = rtrim($url, '/');
    }
    
    public static function setRouter($router) {
        self::$router = $router;
    }
    
    public static function url($path) {
        return self::$baseUrl . '/' . ltrim($path, '/');
    }
    
    public static function route($name, $params = []) {
        if (self::$router && method_exists(self::$router, 'url')) {
            return self::$baseUrl . self::$router->url($name, $params);
        }
        
        throw new Exception("Router not configured for named routes");
    }
    
    public static function asset($path) {
        return self::$baseUrl . '/assets/' . ltrim($path, '/');
    }
    
    public static function redirect($url, $code = 302) {
        if (strpos($url, 'http') !== 0) {
            $url = self::url($url);
        }
        
        http_response_code($code);
        header("Location: {$url}");
        exit;
    }
    
    public static function currentUrl() {
        $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
        $host = $_SERVER['HTTP_HOST'];
        $uri = $_SERVER['REQUEST_URI'];
        
        return "{$protocol}://{$host}{$uri}";
    }
    
    public static function previousUrl() {
        return $_SERVER['HTTP_REFERER'] ?? self::url('/');
    }
}

// Configuration
UrlHelper::setBaseUrl('https://example.com');

// Usage in templates
echo UrlHelper::url('/user/profile'); // https://example.com/user/profile
echo UrlHelper::asset('css/style.css'); // https://example.com/assets/css/style.css

// Redirects
UrlHelper::redirect('/login'); // Redirect to login page
UrlHelper::redirect('/dashboard', 301); // Permanent redirect
?>

.htaccess Configuration

Apache Rewrite Rules

# .htaccess file for clean URLs

RewriteEngine On

# Redirect all requests to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

# Optional: Force HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

# Optional: Remove trailing slashes
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [R=301,L]

# Optional: Remove index.php from URLs
RewriteCond %{THE_REQUEST} /index\.php[?\s] [NC]
RewriteRule ^index\.php$ / [R=301,L]

Nginx Configuration

# Nginx configuration for clean URLs

server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    index index.php;

    # Handle clean URLs
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # PHP processing
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Deny access to sensitive files
    location ~ /\. {
        deny all;
    }
}

Error Handling and Custom Pages

Custom Error Pages

<?php
class ErrorHandler {
    public static function handle404() {
        http_response_code(404);
        
        // Check if custom 404 template exists
        if (file_exists('templates/404.php')) {
            include 'templates/404.php';
        } else {
            echo self::getDefault404();
        }
    }
    
    public static function handle500($message = null) {
        http_response_code(500);
        
        if (file_exists('templates/500.php')) {
            include 'templates/500.php';
        } else {
            echo self::getDefault500($message);
        }
    }
    
    private static function getDefault404() {
        return '
        <!DOCTYPE html>
        <html>
        <head>
            <title>Page Not Found</title>
            <style>
                body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
                h1 { color: #333; }
            </style>
        </head>
        <body>
            <h1>404 - Page Not Found</h1>
            <p>The page you are looking for could not be found.</p>
            <a href="/">Go back to homepage</a>
        </body>
        </html>';
    }
    
    private static function getDefault500($message) {
        return '
        <!DOCTYPE html>
        <html>
        <head>
            <title>Server Error</title>
            <style>
                body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
                h1 { color: #d32f2f; }
            </style>
        </head>
        <body>
            <h1>500 - Internal Server Error</h1>
            <p>Something went wrong on our end.</p>
            ' . ($message ? "<p>Error: {$message}</p>" : '') . '
            <a href="/">Go back to homepage</a>
        </body>
        </html>';
    }
}

// Enhanced router with error handling
class RobustRouter extends SimpleRouter {
    protected function handle404() {
        ErrorHandler::handle404();
    }
    
    protected function callHandler($handler, $params) {
        try {
            return parent::callHandler($handler, $params);
        } catch (Exception $e) {
            error_log("Router error: " . $e->getMessage());
            ErrorHandler::handle500($e->getMessage());
        }
    }
}
?>

Performance Optimization

Route Caching

<?php
class CachedRouter {
    private $routes = [];
    private $cacheFile;
    
    public function __construct($cacheFile = 'cache/routes.php') {
        $this->cacheFile = $cacheFile;
        $this->loadFromCache();
    }
    
    public function addRoute($method, $pattern, $handler) {
        $this->routes[] = [
            'method' => $method,
            'pattern' => $pattern,
            'handler' => $handler
        ];
    }
    
    public function saveToCache() {
        $cacheDir = dirname($this->cacheFile);
        if (!is_dir($cacheDir)) {
            mkdir($cacheDir, 0755, true);
        }
        
        $content = "<?php\nreturn " . var_export($this->routes, true) . ";\n";
        file_put_contents($this->cacheFile, $content);
    }
    
    private function loadFromCache() {
        if (file_exists($this->cacheFile)) {
            $this->routes = include $this->cacheFile;
        }
    }
    
    public function clearCache() {
        if (file_exists($this->cacheFile)) {
            unlink($this->cacheFile);
        }
    }
    
    // Rest of the router implementation...
}

// Precompiled route matching for better performance
class FastRouter {
    private $staticRoutes = [];
    private $dynamicRoutes = [];
    
    public function addRoute($method, $pattern, $handler) {
        if (strpos($pattern, '{') === false && strpos($pattern, '(') === false) {
            // Static route
            $this->staticRoutes[$method][$pattern] = $handler;
        } else {
            // Dynamic route
            $this->dynamicRoutes[$method][] = [
                'pattern' => $pattern,
                'handler' => $handler,
                'compiled' => $this->compilePattern($pattern)
            ];
        }
    }
    
    public function dispatch($requestUri, $requestMethod) {
        // Check static routes first (faster)
        if (isset($this->staticRoutes[$requestMethod][$requestUri])) {
            return $this->callHandler($this->staticRoutes[$requestMethod][$requestUri], []);
        }
        
        // Check dynamic routes
        if (isset($this->dynamicRoutes[$requestMethod])) {
            foreach ($this->dynamicRoutes[$requestMethod] as $route) {
                if (preg_match($route['compiled'], $requestUri, $matches)) {
                    array_shift($matches);
                    return $this->callHandler($route['handler'], $matches);
                }
            }
        }
        
        $this->handle404();
    }
    
    private function compilePattern($pattern) {
        return '#^' . preg_replace('/\{[^}]+\}/', '([^/]+)', $pattern) . '$#';
    }
    
    // Rest of implementation...
}
?>

URL routing is essential for modern web applications, providing clean URLs, better organization, and improved user experience. Choose the complexity level that matches your project's needs, from simple pattern matching to full-featured routing systems with middleware and caching.