Build a Simple PHP Router from Scratch: Step‑by‑Step Guide
This tutorial walks you through creating a lightweight PHP router—from understanding routing fundamentals and setting up project structure to writing the core Router class, configuring URL rewriting, defining routes with parameters, testing, and exploring extensions—empowering you to grasp framework internals and handle HTTP requests without a full‑stack framework.
In modern PHP web development, frameworks like Laravel or Symfony provide powerful routers that map HTTP requests to controllers. Understanding how a router works deepens your knowledge of these frameworks and lets you build lightweight projects without a full‑stack solution.
We will build a simple, functional PHP router from scratch.
1. What is a router?
A router is the traffic controller of an application. Its main tasks are:
Parse request: obtain the current request URI (e.g., /about or /contact) and method (GET, POST, etc.).
Match route: find a predefined route rule that matches the URI and method.
Execute action: invoke the associated handler, which can be a closure, a controller method, or a view file.
2. Project structure and preparation
Create a clean project directory using the Front Controller pattern, where all requests go through index.php.
Project layout:
/my_simple_router
│ .htaccess # URL rewrite (Apache)
│ index.php # Front controller, entry point
└───src/
Router.php # Core router classEnvironment requirements: a running Apache server with mod_rewrite enabled (or Nginx) and PHP support.
3. Implementation steps
Step 1: Configure URL rewriting (.htaccess)
Create a .htaccess file in the project root to route all requests to index.php:
RewriteEngine On
# If the request is not an existing file or directory
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Rewrite to index.php
RewriteRule ^(.*)$ index.php [QSA,L]This configuration forwards non‑existent URLs to index.php for routing.
Step 2: Create the core router class (src/Router.php)
The class stores route definitions and performs matching.
<?php
class Router
{
// Store all registered routes
private $routes = [];
// Register a GET route
public function get($path, $callback)
{
$this->addRoute('GET', $path, $callback);
}
// Register a POST route
public function post($path, $callback)
{
$this->addRoute('POST', $path, $callback);
}
// Generic method to add a route
private function addRoute($method, $path, $callback)
{
$this->routes[] = [
'method' => $method,
'path' => $path,
'callback' => $callback
];
}
// Dispatch the current request – core matching logic
public function dispatch()
{
// Get current request URI and method
$requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$requestMethod = $_SERVER['REQUEST_METHOD'];
// Remove base path if the project is not in the web root
$basePath = '/my_simple_router'; // adjust or set to '' if in root
if ($basePath && strpos($requestUri, $basePath) === 0) {
$requestUri = substr($requestUri, strlen($basePath));
}
// Iterate over registered routes and try to match
foreach ($this->routes as $route) {
// Convert route path to a regex, supporting dynamic parameters like /user/{id}
$pattern = '#^' . preg_replace('/\{([a-zA-Z0-9_]+)\}/', '([a-zA-Z0-9_]+)', $route['path']) . '$#';
// Check method and path match
if ($route['method'] === $requestMethod && preg_match($pattern, $requestUri, $matches)) {
// Remove the full match, keep captured parameters
array_shift($matches);
// Call the callback
if (is_callable($route['callback'])) {
call_user_func_array($route['callback'], $matches);
} else {
// Could be expanded to handle controller strings like 'HomeController@index'
echo "Callback is not callable.";
}
return; // stop after a successful match
}
}
// No matching route – return 404
$this->sendNotFound();
}
// Send a 404 response
private function sendNotFound()
{
header("HTTP/1.0 404 Not Found");
echo '404 - Page not found.';
exit;
}
}Code explanation:
$routes: array that stores all defined routes. get() and post(): convenience methods for registering routes for specific HTTP methods. addRoute(): stores method, path, and callback in $routes. dispatch(): core method that obtains the current URI and method, converts each route path to a regex (supporting dynamic segments like {id}), matches them, and invokes the corresponding callback with captured parameters; if none match, it sends a 404.
Step 3: Create the front controller (index.php)
Use the router in index.php:
<?php
require_once 'src/Router.php';
$router = new Router();
// 1. Define basic routes
$router->get('/', function () {
echo "<h1>Welcome to the Homepage!</h1>";
});
$router->get('/about', function () {
echo "<h1>About Us</h1><p>This is a simple PHP router.</p>";
});
// 2. Define a route with a parameter (e.g., /user/123)
$router->get('/user/{id}', function ($id) {
echo "<h1>User Profile</h1><p>Showing profile for User ID: $id</p>";
});
// 3. Define a POST route (e.g., login form handling)
$router->post('/login', function () {
$username = $_POST['username'] ?? 'Unknown';
echo "<p>Thank you for logging in, $username!</p>";
});
// ... more routes can be added here
// Finally, let the router handle the current request
$router->dispatch();4. Test your router
Homepage: visit http://localhost/my_simple_router/ – you should see “Welcome to the Homepage!”.
About page: visit http://localhost/my_simple_router/about – displays the about page.
Dynamic route: visit http://localhost/my_simple_router/user/456 – shows “Showing profile for User ID: 456”.
POST request: create a simple HTML form with action set to /my_simple_router/login and method set to post; submitting should display the greeting.
404 page: visit an undefined path such as http://localhost/my_simple_router/unknown – you will see a 404 error.
5. Extension ideas
Support more HTTP methods: add put(), patch(), delete(), etc.
Controller support: modify dispatch to resolve strings like 'HomeController@index' and automatically instantiate the controller.
Complex route patterns: allow regex constraints on parameters (e.g., {id:\d+}).
Middleware: plug in logic before or after route execution for authentication, logging, etc.
Named routes: assign names to routes for easier URL generation.
Conclusion
Through this exercise we built the core engine of a PHP router. You learned how to parse URIs, match routes, handle dynamic parameters, and gain insight into the inner workings of modern PHP frameworks. This lightweight router is sufficient for many small projects, and understanding these fundamentals makes advanced framework routing documentation much more approachable.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
php Courses
php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
