17: Building a Basic HTTP Router

Chapter 17: Building a Basic HTTP Router
Section titled “Chapter 17: Building a Basic HTTP Router”Overview
Section titled “Overview”If you look at the URL of any modern web application, you’ll see clean, human-readable paths like /users/123 or /posts/my-first-post. You won’t see file names like view-user.php?id=123. How does this work?
Modern applications use a design pattern called a Front Controller. This means that every request to the application is actually handled by a single PHP file (usually index.php). This file doesn’t contain the page logic itself. Instead, it contains a Router.
A router is a piece of code that inspects the incoming request URL (the URI) and the HTTP method (GET, POST, etc.) and decides which part of your application’s code should be executed. It maps a URL to a specific function or a method on a controller class.
In this chapter, you’ll build a simple but effective router from scratch. This is a crucial step towards building our blog application with a clean, modern architecture.
Prerequisites
Section titled “Prerequisites”Before starting this chapter, you should have:
- Completed Chapter 16: Writing Better Code with PSR-1 and PSR-12
- PHP 8.4 installed and working
- Composer installed
- Basic understanding of classes, namespaces, and autoloading
- A
simple-blogproject directory with Composer initialized - Time required: ~35 minutes
What You’ll Build
Section titled “What You’ll Build”By the end of this chapter, you’ll have:
- A
Routerclass that registers routes (URL patterns → handlers) - Support for different HTTP methods (GET, POST)
- Dynamic route parameters (e.g.,
/posts/{id},/users/{username}) - Automatic parameter extraction and passing to handlers
- A front controller (
public/index.php) that handles all requests - Clean URL routing (e.g.,
/about,/contact) without file extensions - A 404 handler for undefined routes
- A project structure that follows modern PHP application conventions
Objectives
Section titled “Objectives”- Understand the Front Controller pattern
- Get the request URI and method from the
$_SERVERsuperglobal - Create a
Routerclass that can register routes - Dispatch a request to the correct route’s handler
- Handle 404 Not Found errors for undefined routes
Router Architecture: How It Works
Section titled “Router Architecture: How It Works”The Front Controller pattern and routing work through a series of steps that handle every incoming request:
The Request Flow:
- Browser Request - A user visits a URL like
/posts/123 - Single Entry Point - All requests are directed to
index.php(the front controller) - Parse Request - The front controller extracts the URI path and HTTP method from the request
- Router Matching - The router compares the request URI against all registered route patterns
- Route Decision - If a matching route is found, continue to parameter extraction; if not, jump to 404 handling
- Extract Parameters - For dynamic routes like
/posts/{id}, extract the parameter values (e.g.,id = 123) - Execute Handler - Call the route’s handler function or controller method with the extracted parameters
- Generate Response - The handler produces HTML, JSON, or other output
- Send to Browser - The response is sent back to the user
Key Concepts:
- Single Entry Point: All requests go through
index.php, giving you centralized control - URL Matching: The router compares the request URI to registered patterns using string comparison or regex
- Dynamic Parameters: Routes can extract values like
{id}from URLs and pass them to handlers - Handler Execution: The matched route’s function or method is called with extracted parameters
- 404 Fallback: Unmatched routes are handled gracefully with a “Not Found” response
Step 1: Setting Up the Project Structure (~5 min)
Section titled “Step 1: Setting Up the Project Structure (~5 min)”Goal: Create a modern project structure with a public entry point and separate directories for application code.
Our router needs a single entry point. By configuring our web server to redirect all requests to index.php, we can ensure our router handles everything.
Actions
Section titled “Actions”-
Navigate to your project directory:
Terminal window # If you don't have a simple-blog directory yet, create itmkdir -p simple-blogcd simple-blog -
Create the directory structure:
Terminal window # Create public directory (web-accessible root)mkdir -p public# Create src/Core directory (application code)mkdir -p src/Core -
Create initial empty files:
Terminal window # Create the front controllertouch public/index.php# Create the Router class filetouch src/Core/Router.php -
Initialize Composer (if not already done):
Terminal window composer init --name="yourname/simple-blog" --type=project --no-interaction -
Configure autoloading in
composer.json:Open
composer.jsonand add the autoload section:
{ "name": "yourname/simple-blog", "type": "project", "require": { "php": "^8.4" }, "autoload": { "psr-4": { "App\\": "src/" } }}-
Generate the autoloader:
Terminal window composer dump-autoload
Expected Result
Section titled “Expected Result”Your structure should now look like this:
simple-blog/├── public/│ └── index.php├── src/│ └── Core/│ └── Router.php├── vendor/├── composer.json└── composer.lockValidation
Section titled “Validation”Check your structure with:
# Verify directories existls -la
# Should output something like:# drwxr-xr-x public# drwxr-xr-x src# drwxr-xr-x vendor# -rw-r--r-- composer.json# -rw-r--r-- composer.lockWhy It Works
Section titled “Why It Works”This structure follows the separation of concerns principle:
public/is the only directory exposed to the web server (security benefit)src/contains your application logic (inaccessible directly from the browser)vendor/holds third-party dependencies (also protected from direct access)
Now, when you visit http://localhost:8000/, the server will execute public/index.php. When you visit http://localhost:8000/some/other/path, it will also execute public/index.php. Our router can then inspect that path and decide what to do.
Step 2: The Router Class - Registering Routes (~6 min)
Section titled “Step 2: The Router Class - Registering Routes (~6 min)”Goal: Create a Router class that can store route definitions (URL patterns mapped to handlers).
Let’s build our Router class. Its first job is to provide a way to register, or “define,” the routes our application will respond to.
Actions
Section titled “Actions”Open src/Core/Router.php and add the following code:
File: src/Core/Router.php
<?php
declare(strict_types=1);
namespace App\Core;
class Router{ // A private property to hold all our registered routes private array $routes = [];
/** * Add a route to the routing table. * * @param string $method The HTTP method (GET, POST, etc.) * @param string $uri The URI pattern to match (e.g., '/about') * @param callable $handler The function or controller to execute */ public function add(string $method, string $uri, callable $handler): void { $this->routes[] = [ 'method' => $method, 'uri' => $uri, 'handler' => $handler, ]; }
/** * Register a GET route. */ public function get(string $uri, callable $handler): void { $this->add('GET', $uri, $handler); }
/** * Register a POST route. */ public function post(string $uri, callable $handler): void { $this->add('POST', $uri, $handler); }}Expected Result
Section titled “Expected Result”You should now have a Router class with three public methods: add(), get(), and post().
Why It Works
Section titled “Why It Works”private array $routes = []: Stores all registered routes as an array of associative arrays. Each route has three keys: method, uri, and handler.callabletype hint: In PHP,callableensures the parameter is something that can be called (a function, closure, or method).- Convenience methods:
get()andpost()are shortcuts that internally calladd()with the appropriate HTTP method. This makes our routing code more readable:$router->get('/about', ...)is clearer than$router->add('GET', '/about', ...).
::: tip
The callable type was chosen over mixed or Closure because it’s more specific while still allowing flexibility for future enhancements (like array-based controller references).
:::
Step 3: The Router Class - Dispatching the Request (~7 min)
Section titled “Step 3: The Router Class - Dispatching the Request (~7 min)”Goal: Add logic to match incoming requests against registered routes and execute the appropriate handler.
The router’s second job is to look at the current request and find a matching route in its table. This is called dispatching.
Actions
Section titled “Actions”Add the dispatch() method to your Router class. Here’s the complete file with the new method:
File: src/Core/Router.php (complete)
<?php
declare(strict_types=1);
namespace App\Core;
class Router{ // A private property to hold all our registered routes private array $routes = [];
/** * Add a route to the routing table. * * @param string $method The HTTP method (GET, POST, etc.) * @param string $uri The URI pattern to match (e.g., '/about') * @param callable $handler The function or controller to execute */ public function add(string $method, string $uri, callable $handler): void { $this->routes[] = [ 'method' => $method, 'uri' => $uri, 'handler' => $handler, ]; }
/** * Register a GET route. */ public function get(string $uri, callable $handler): void { $this->add('GET', $uri, $handler); }
/** * Register a POST route. */ public function post(string $uri, callable $handler): void { $this->add('POST', $uri, $handler); }
/** * Dispatch the current request to the matching route handler. * * @return mixed The result of the handler execution */ public function dispatch(): mixed { // 1. Get the current request URI and method $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $method = $_SERVER['REQUEST_METHOD'];
// 2. Loop through registered routes to find a match foreach ($this->routes as $route) { // 3. Check if both URI and method match if ($route['uri'] === $uri && $route['method'] === $method) { // 4. Execute the matched handler and return its result return call_user_func($route['handler']); } }
// 5. No route matched - send 404 response $this->handleNotFound(); }
/** * Handle 404 Not Found errors. */ private function handleNotFound(): void { http_response_code(404); echo "404 Not Found"; }}Expected Result
Section titled “Expected Result”Your Router class now has the ability to match incoming requests against registered routes and execute the correct handler.
Why It Works
Section titled “Why It Works”$_SERVER['REQUEST_URI']: A superglobal containing the requested URI, including query strings (e.g.,/about?foo=bar).parse_url($uri, PHP_URL_PATH): Extracts just the path portion of the URI, ignoring query strings. Using the constantPHP_URL_PATHis more explicit and safer than array access.$_SERVER['REQUEST_METHOD']: Contains the HTTP method used (GET, POST, PUT, DELETE, etc.).- Route matching: The foreach loop compares the incoming URI and method against each registered route.
call_user_func(): A built-in PHP function that executes a callable (function, closure, method). It’s flexible and works with various callable types.- Separation of concerns: The
handleNotFound()private method isolates 404 handling, making it easier to customize later.
::: warning
This simple router uses exact string matching. It doesn’t support dynamic segments like /users/{id} yet. You’ll learn about that in advanced routing chapters.
:::
Step 4: Creating the Front Controller (~5 min)
Section titled “Step 4: Creating the Front Controller (~5 min)”Goal: Wire up the router in public/index.php to handle all incoming requests.
Now that we have a working router, let’s use it in our front controller—the single entry point for all requests.
Actions
Section titled “Actions”Open public/index.php and add the following code:
File: public/index.php
<?php
declare(strict_types=1);
// Load Composer's autoloaderrequire_once __DIR__ . '/../vendor/autoload.php';
use App\Core\Router;
// 1. Create a new router instance$router = new Router();
// 2. Define application routes$router->get('/', function () { echo "<h1>Welcome to Simple Blog</h1>"; echo "<p>This is the Home page.</p>";});
$router->get('/about', function () { echo "<h1>About Us</h1>"; echo "<p>This is the About page.</p>";});
$router->get('/contact', function () { echo "<h1>Contact</h1>"; echo "<p>This is the Contact page.</p>";});
// 3. Dispatch the current request$router->dispatch();Expected Result
Section titled “Expected Result”Your application now has three working routes. The router will inspect each incoming request and execute the matching handler.
Validation
Section titled “Validation”- Start the PHP built-in server:
# Run from the simple-blog root directoryphp -S localhost:8000 -t publicYou should see output like:
[Sat Oct 25 10:30:00 2025] PHP 8.4.0 Development Server (http://localhost:8000) started- Test each route:
# In a new terminal, test with curlcurl http://localhost:8000/
# Expected output:# <h1>Welcome to Simple Blog</h1># <p>This is the Home page.</p>- Test in your browser:
- Visit
http://localhost:8000/→ You should see “Welcome to Simple Blog” - Visit
http://localhost:8000/about→ You should see “About Us” - Visit
http://localhost:8000/contact→ You should see “Contact” - Visit
http://localhost:8000/fake-page→ You should see “404 Not Found”
- Visit
Why It Works
Section titled “Why It Works”__DIR__: A magic constant containing the directory of the current file. Using it makes the autoload path work regardless of where PHP is executed from.- Front Controller Pattern: Every request—no matter the URL—hits
public/index.phpfirst. The router then decides what code to run based on the URI. - Closures as Handlers: The anonymous functions (
function() { ... }) are called “closures.” They’re perfect for simple route handlers. - The
-t publicflag: Tells PHP’s built-in server thatpublic/is the document root, so requests go topublic/index.php.
Troubleshooting
Section titled “Troubleshooting”| Problem | Possible Cause | Solution |
|---|---|---|
| ”Class ‘App\Core\Router’ not found” | Autoloader not generated | Run composer dump-autoload |
| Server starts but shows blank page | PHP error occurred | Check terminal for error messages; ensure display_errors=On |
404 for all pages including / | Server not using public/ as root | Verify you used -t public flag when starting server |
| ”Failed to listen on localhost:8000” | Port already in use | Use a different port: php -S localhost:8080 -t public |
Step 5: Adding Dynamic Route Parameters (~8 min)
Section titled “Step 5: Adding Dynamic Route Parameters (~8 min)”Goal: Enable the router to handle dynamic URL segments like /posts/{id} or /users/{username}.
Right now, our router only supports exact string matching. But what if you want to build a route like /posts/123 where 123 is a blog post ID? You need dynamic route parameters.
The Problem
Section titled “The Problem”With our current router, you’d need to register every possible route manually:
$router->get('/posts/1', function() { /* ... */ });$router->get('/posts/2', function() { /* ... */ });$router->get('/posts/3', function() { /* ... */ });// This is clearly unsustainable!The Solution
Section titled “The Solution”We’ll use placeholder syntax like /posts/{id} that matches any value in that position and passes it to the handler.
Actions
Section titled “Actions”Replace the entire Router class with this enhanced version:
File: src/Core/Router.php (complete with parameters support)
<?php
declare(strict_types=1);
namespace App\Core;
class Router{ // A private property to hold all our registered routes private array $routes = [];
/** * Add a route to the routing table. * * @param string $method The HTTP method (GET, POST, etc.) * @param string $uri The URI pattern to match (e.g., '/about' or '/posts/{id}') * @param callable $handler The function or controller to execute */ public function add(string $method, string $uri, callable $handler): void { $this->routes[] = [ 'method' => $method, 'uri' => $uri, 'handler' => $handler, ]; }
/** * Register a GET route. */ public function get(string $uri, callable $handler): void { $this->add('GET', $uri, $handler); }
/** * Register a POST route. */ public function post(string $uri, callable $handler): void { $this->add('POST', $uri, $handler); }
/** * Dispatch the current request to the matching route handler. * * @return mixed The result of the handler execution */ public function dispatch(): mixed { // 1. Get the current request URI and method $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $method = $_SERVER['REQUEST_METHOD'];
// 2. Loop through registered routes to find a match foreach ($this->routes as $route) { // 3. Check if the method matches first if ($route['method'] !== $method) { continue; }
// 4. Check if the URI matches (either exact or with parameters) $params = $this->matchRoute($route['uri'], $uri);
if ($params !== false) { // 5. We have a match! Execute the handler with parameters $handler = $route['handler'];
// If params is empty array, call without arguments if (empty($params)) { return call_user_func($handler); }
// Otherwise, pass the extracted parameters return call_user_func_array($handler, $params); } }
// 6. No route matched - send 404 response $this->handleNotFound(); }
/** * Check if a route pattern matches the request URI and extract parameters. * * @param string $routeUri The route pattern (e.g., '/posts/{id}') * @param string $requestUri The actual request URI (e.g., '/posts/123') * @return array|false Array of parameters if matched, false otherwise */ private function matchRoute(string $routeUri, string $requestUri): array|false { // Exact match - no parameters if ($routeUri === $requestUri) { return []; }
// Convert route pattern to regex // Example: '/posts/{id}' becomes '/posts/([^/]+)' $pattern = preg_replace('/\{[a-zA-Z0-9_]+\}/', '([^/]+)', $routeUri);
// Escape forward slashes for regex $pattern = '#^' . $pattern . '$#';
// Try to match the pattern against the request URI if (preg_match($pattern, $requestUri, $matches)) { // Remove the full match (first element), keep only captured groups array_shift($matches); return $matches; }
return false; }
/** * Handle 404 Not Found errors. */ private function handleNotFound(): void { http_response_code(404); echo "404 Not Found"; }}Expected Result
Section titled “Expected Result”Your router can now handle both static routes and dynamic routes with parameters.
Validation
Section titled “Validation”Let’s test the new functionality. Update public/index.php to include a dynamic route:
<?php
declare(strict_types=1);
// Load Composer's autoloaderrequire_once __DIR__ . '/../vendor/autoload.php';
use App\Core\Router;
// 1. Create a new router instance$router = new Router();
// 2. Define static routes$router->get('/', function () { echo "<h1>Welcome to Simple Blog</h1>"; echo "<p>This is the Home page.</p>";});
$router->get('/about', function () { echo "<h1>About Us</h1>"; echo "<p>This is the About page.</p>";});
// 3. Define dynamic routes with parameters$router->get('/posts/{id}', function ($id) { echo "<h1>Viewing Post #{$id}</h1>"; echo "<p>This is where post {$id} content would be displayed.</p>";});
$router->get('/users/{username}', function ($username) { echo "<h1>User Profile: {$username}</h1>"; echo "<p>Welcome to {$username}'s profile page!</p>";});
// Multiple parameters$router->get('/blog/{year}/{month}', function ($year, $month) { echo "<h1>Blog Archive</h1>"; echo "<p>Showing posts from {$month}/{$year}</p>";});
// 4. Dispatch the current request$router->dispatch();Now test these URLs:
# Static routes still workcurl http://localhost:8000/curl http://localhost:8000/about
# Dynamic routes with single parametercurl http://localhost:8000/posts/123curl http://localhost:8000/posts/456curl http://localhost:8000/users/johncurl http://localhost:8000/users/jane
# Multiple parameterscurl http://localhost:8000/blog/2025/10You should see output like:
/posts/123→ “Viewing Post #123”/users/john→ “User Profile: john”/blog/2025/10→ “Showing posts from 10/2025”
Why It Works
Section titled “Why It Works”Let’s break down the key components:
-
matchRoute()Method:- Converts route patterns like
/posts/{id}into regex patterns like/posts/([^/]+) - The
{id}placeholder becomes([^/]+)which means “capture one or more characters that aren’t a forward slash” - Uses
preg_match()to test if the request URI matches the pattern
- Converts route patterns like
-
Parameter Extraction:
preg_match()stores captured groups (the parameter values) in the$matchesarray- We remove the first element (full match) and return only the captured parameters
- Example: For pattern
/posts/{id}matching/posts/123, we get['123']
-
call_user_func_array():- Similar to
call_user_func(), but accepts an array of arguments - Passes the extracted parameters to the handler function
- If route is
/posts/{id}and URL is/posts/123, the handler receives$id = '123'
- Similar to
-
Placeholder Naming:
- Placeholder names like
{id},{username},{slug}are for readability - The actual name doesn’t matter for matching—only the position
- Parameters are passed to handlers in the order they appear in the URL
- Placeholder names like
::: tip Type Safety
Notice parameters come in as strings. If you need an integer, cast it: (int)$id. In production applications, you’d validate and sanitize these values.
:::
::: warning
This implementation doesn’t support optional parameters or regex constraints (e.g., {id:\d+}). For more complex routing needs, consider using a dedicated routing library like FastRoute.
:::
Troubleshooting
Section titled “Troubleshooting”| Problem | Possible Cause | Solution |
|---|---|---|
Parameters are null or empty | Handler signature doesn’t match | Ensure handler has parameter: function($id) not function() |
| Route not matching expected pattern | Typo in placeholder syntax | Use {paramName} not [paramName] or <paramName> |
| Wrong parameter values | Route order matters | More specific routes should be registered before generic ones |
| ”Too few arguments” error | Missing handler parameter | Add parameter to handler: function($id, $slug) |
Exercises
Section titled “Exercises”Test your understanding by extending the router with these challenges.
Exercise 1: Product Catalog with Dynamic Routes (Intermediate)
Section titled “Exercise 1: Product Catalog with Dynamic Routes (Intermediate)”Build a simple product catalog using dynamic routing:
- Create a
/productsroute that displays a list of products (use an array with id, name, price) - Create a
/products/{id}route that displays details for a specific product - In the handler, look up the product by ID from your array
- Display the product’s name, price, and description
Hint:
$products = [ 1 => ['name' => 'Laptop', 'price' => 999.99, 'description' => 'Powerful laptop'], 2 => ['name' => 'Mouse', 'price' => 29.99, 'description' => 'Wireless mouse'], 3 => ['name' => 'Keyboard', 'price' => 79.99, 'description' => 'Mechanical keyboard'],];
$router->get('/products/{id}', function($id) use ($products) { $id = (int)$id; if (!isset($products[$id])) { echo "Product not found"; return; } $product = $products[$id]; echo "<h1>{$product['name']}</h1>"; echo "<p>Price: \${$product['price']}</p>"; echo "<p>{$product['description']}</p>";});Expected outcome: Visiting /products/1 shows laptop details, /products/2 shows mouse details, etc.
Exercise 2: Handle POST Requests (Intermediate)
Section titled “Exercise 2: Handle POST Requests (Intermediate)”Create a simple contact form:
- Create a
public/form.htmlfile:
<!DOCTYPE html><html> <head> <title>Contact Form</title> </head> <body> <h1>Contact Us</h1> <form action="/submit-form" method="POST"> <label>Name: <input type="text" name="name" required /></label><br /> <label>Email: <input type="email" name="email" required /></label><br /> <label>Message: <textarea name="message" required></textarea></label ><br /> <button type="submit">Submit</button> </form> </body></html>- Add a GET route in
public/index.phpto serve the form:
$router->get('/form', function () { echo file_get_contents(__DIR__ . '/form.html');});- Add a POST route to handle the form submission:
$router->post('/submit-form', function () { $name = $_POST['name'] ?? 'Unknown'; $email = $_POST['email'] ?? 'Unknown'; $message = $_POST['message'] ?? 'No message';
echo "<h1>Thank You!</h1>"; echo "<p>We received your message, $name.</p>"; echo "<p>We'll reply to: $email</p>";});Test it: Visit http://localhost:8000/form, fill out the form, and submit it.
Exercise 3: Add HTTP Method Handlers (Advanced)
Section titled “Exercise 3: Add HTTP Method Handlers (Advanced)”Add support for PUT and DELETE methods to your router:
- Add these methods to
Router.php:
public function put(string $uri, callable $handler): void{ $this->add('PUT', $uri, $handler);}
public function delete(string $uri, callable $handler): void{ $this->add('DELETE', $uri, $handler);}- Test with curl:
# Test DELETEcurl -X DELETE http://localhost:8000/users/123
# Test PUTcurl -X PUT http://localhost:8000/users/123 -d "name=John"Exercise 4: Improve 404 Handling (Intermediate)
Section titled “Exercise 4: Improve 404 Handling (Intermediate)”Modify the handleNotFound() method to display a custom HTML 404 page:
private function handleNotFound(): void{ http_response_code(404); echo <<<HTML <!DOCTYPE html> <html> <head> <title>404 Not Found</title> <style> body { font-family: sans-serif; text-align: center; padding: 50px; } h1 { color: #e74c3c; } </style> </head> <body> <h1>404 - Page Not Found</h1> <p>The page you're looking for doesn't exist.</p> <a href="/">Go Home</a> </body> </html> HTML;}Wrap-up
Section titled “Wrap-up”Congratulations! You’ve just built the foundation of a modern PHP web application. Let’s recap what you’ve accomplished:
What You’ve Learned
Section titled “What You’ve Learned”-
Front Controller Pattern: All requests funnel through a single entry point (
public/index.php), giving you centralized control over routing and request handling. -
Router Implementation: You built a flexible
Routerclass that:- Registers routes with HTTP methods and URI patterns
- Matches incoming requests against registered routes
- Supports dynamic route parameters like
/posts/{id} - Extracts parameters and passes them to handlers automatically
- Executes the appropriate handler
- Handles 404 errors gracefully
-
Clean URLs: Your application now uses human-readable URLs like
/about,/contact, and/posts/123instead of file-based URLs likeabout.phporview-post.php?id=123. -
Modern Project Structure: You organized code into:
public/for web-accessible files (entry point only)src/Core/for framework-level code- Composer autoloading for clean class imports
-
Type Safety: You used PHP 8.4’s type system with
string,callable,void, andmixedtypes to make code more robust.
Key Concepts
Section titled “Key Concepts”- Routing: Mapping URLs to code that should execute
- Dispatching: Finding and executing the correct handler for a request
- Dynamic Parameters: Extracting variable segments from URLs (e.g.,
/posts/{id}) - Regular Expressions: Using regex patterns to match flexible URL structures
- Superglobals:
$_SERVERprovides request information - Closures: Anonymous functions used as route handlers
- Separation of Concerns: Keeping routing logic separate from business logic
Real-World Context
Section titled “Real-World Context”Every major PHP framework (Laravel, Symfony, Slim) uses this same pattern. The routing concepts you’ve learned here translate directly:
// Your router$router->get('/users', function() { /* ... */ });
// LaravelRoute::get('/users', function() { /* ... */ });
// Symfony$routes->add('users', new Route('/users', /* ... */));Limitations & Next Steps
Section titled “Limitations & Next Steps”Your router now supports the fundamentals, but there are still advanced features used in production frameworks:
- Named routes: Giving routes names for easier URL generation
- Route constraints: Restricting parameters with regex (e.g.,
{id:\d+}for digits only) - Optional parameters: Routes like
/blog/{year?}/{month?} - Middleware: Running code before/after handlers (authentication, logging, etc.)
- Route groups: Organizing related routes with shared prefixes or middleware
- HTTP method spoofing: Supporting PUT/DELETE from HTML forms
- Controller classes: Separating routing from business logic
The next chapters will cover controller classes and MVC architecture as we continue building our blog application.
What’s Next
Section titled “What’s Next”In Chapter 18: Project - Structuring a Simple Application, you’ll:
- Create a proper MVC (Model-View-Controller) structure
- Build reusable controller classes
- Implement a simple templating system
- Connect your router to controllers instead of closures
- Add view rendering for cleaner separation of logic and presentation
Knowledge Check
Section titled “Knowledge Check”Test your understanding of HTTP routing:
Further Reading
Section titled “Further Reading”- PSR-7: HTTP Message Interfaces - Standard for HTTP requests/responses
- PSR-15: HTTP Server Request Handlers - Middleware standard
- FastRoute - High-performance router library
- PHP-FIG - PHP Framework Interoperability Group standards