The Goal: Handling Dynamic URLs
Our current router is great, but it has one major limitation: it only matches exact, static paths like /
. Real-world applications need to handle dynamic URLs where parts of the path are variables. For example:
/posts/my-first-post
- where "my-first-post" is a dynamic slug./users/123
- where "123" is a dynamic user ID.
In this lesson, we will upgrade our router to handle these dynamic segments using Regular Expressions (Regex).
Step 1: Upgrading the Router Class
We need to completely rewrite our dispatch()
method. The new version will convert our route paths (like /post/{slug}
) into a Regex pattern that can be used to match against the incoming URL and extract the dynamic parts.
Replace the entire content of app/Core/Router.php
with this new code:
<?php
namespace App\Core;
class Router
{
protected $routes = [];
protected $params = [];
public function add($route, $controller, $method)
{
// Convert the route to a regular expression: escape forward slashes
$route = preg_replace('/\//', '\\/', $route);
// Convert variables e.g. {slug}
$route = preg_replace('/\{([a-z]+)\}/', '(?P<\1>[a-z0-9-]+)', $route);
// Add start and end delimiters, and case-insensitive flag
$route = '/^' . $route . '$/i';
$this->routes[$route] = ['controller' => $controller, 'method' => $method];
}
public function dispatch()
{
$uri = strtok($_SERVER['REQUEST_URI'], '?');
foreach ($this->routes as $route => $params) {
if (preg_match($route, $uri, $matches)) {
// Remove the full URL match
array_shift($matches);
$this->params = $matches;
$controller = "App\\Controllers\\" . $params['controller'];
$method = $params['method'];
if (class_exists($controller)) {
$controllerInstance = new $controller();
if (method_exists($controllerInstance, $method)) {
call_user_func_array([$controllerInstance, $method], $this->params);
return; // Stop searching once a match is found
}
}
}
}
// If no route was matched
http_response_code(404);
echo "404 Not Found - No route matched for URI: {$uri}";
}
}
How does the new code work?
- The
add()
method now converts a simple path like/post/{slug}
into a complex Regex pattern like/^\/post\/(?P<slug>[a-z0-9-]+)$/i
. This looks scary, but it's just a precise way of defining a URL pattern. - The
dispatch()
method loops through these Regex patterns and usespreg_match
to find a match. If it finds one, it extracts the value of the placeholder (e.g., "my-first-post") and passes it as an argument to the controller method.
Step 2: Creating a PostController to Test The New Router
To test our new router, we need a controller that can accept a dynamic parameter. Let's create a PostController
with a show()
method that accepts a $slug
.
Create a new file: app/Controllers/PostController.php
<?php
namespace App\Controllers;
class PostController
{
/**
* Show a single post.
* The $slug parameter is automatically passed by our Router.
*/
public function show($slug)
{
// For now, we'll just display the slug to prove it's working.
// In the next lesson, we'll use this to fetch data from the database.
echo "This is the show method of PostController. ";
echo "The requested slug is: " . htmlspecialchars($slug);
}
}
Step 3: Registering the Dynamic Route
Finally, let's tell our application about this new dynamic route in our main entry point file.
Update your file: public/index.php
<?php
require_once __DIR__ . '/../config.php';
require_once __DIR__ . '/../vendor/autoload.php';
use App\Core\Router;
use App\Core\App;
App::bind('database', $pdo);
$router = new Router();
// Register routes
$router->add('/', 'HomeController', 'index');
$router->add('/post/{slug}', 'PostController', 'show'); // Our new dynamic route!
// Dispatch the router
$router->dispatch();
Your Mission
- Replace the entire content of
app/Core/Router.php
with the new, upgraded code. - Create the new controller file:
app/Controllers/PostController.php
. - Update
public/index.php
to register the new/post/{slug}
route. - Test it! Open your browser and navigate to a URL like
/post/hello-world
or/post/my-awesome-post-123
. You should see the message from thePostController
, confirming that the slug was successfully captured from the URL.
With this upgrade, our router is now significantly more powerful and can handle most of the routing needs of a typical web application.