Back to Blog
Lesson 45 of the PHP: Modern PHP from the Ground Up course
PHPSeptember 2, 20264 min read

Advanced Routing Constraints: Regex and Dynamic Parameters in PHP

Learn how to use regex in your custom PHP router to capture dynamic URL segments and pass them as parameters to your controllers for flexible, clean routing.

phproutingmvcregexweb-developmentbackend
A close-up view of PHP code displayed on a computer screen, highlighting programming and development concepts.

Previously in this course, we built a Front Controller Pattern and Integrated Routing Logic to map static URLs to specific controllers. While that works for simple sites, real applications need to handle dynamic segments, like /user/123/profile or /post/slug-of-the-article.

In this lesson, we will upgrade our router to support regex-based patterns, allowing us to capture these dynamic segments and pass them directly to our controller methods.

The Problem with Static Routing

Currently, our router likely checks if a URL matches a key in an array:

PHP
$routes = [
    '/about' => 'AboutController@index',
];

If we want to support /user/123, a static approach fails because we would need a unique entry for every possible user ID. Instead, we need a way to define a pattern like /user/{id} where {id} acts as a placeholder that the router identifies and captures.

Implementing Regex in Routing

Vintage wooden signpost in foggy mountain landscape, indicating hiking trails and altitudes.

To handle this, we convert our route patterns into valid Regular Expressions (Regex). For example, the pattern /user/{id} becomes the regex /^\/user\/(?P<id>[0-9]+)$/.

Here is how we integrate this into our Router class.

1. Defining the Route Map

We update our route definition to use regex placeholders:

PHP
#6A9955">// In our routing configuration
$router->add('/user/{id:[0-9]+}', 'UserController@show');

The syntax {id:[0-9]+} tells our router: "Capture this segment as a variable named id, and ensure it only matches digits."

2. The Router Engine

Your Router class needs a method to parse these patterns. We use preg_match to check the URL against the converted regex.

PHP
class Router {
    protected $routes = [];

    public function add($route, $params) {
        #6A9955">// Convert {id:[0-9]+} to(?P<id>[0-9]+)
        $route = preg_replace('/\{([a-z]+):([^\}]+)\}/', '(?P<\1>\2)', $route);
        $route = "/^" . str_replace('/', '\/', $route) . "$/";
        $this->routes[$route] = $params;
    }

    public function dispatch($url) {
        foreach ($this->routes as $route => $params) {
            if (preg_match($route, $url, $matches)) {
                #6A9955">// Filter out numeric keys, keep only named captures
                $params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
                return $this->controller->execute($params);
            }
        }
    }
}

Worked Example: Passing Parameters to Controllers

Now that the router captures the id from the URL, we need to pass it to the controller method.

In your UserController, the show method should accept an array of parameters:

PHP
class UserController {
    public function show(array $params) {
        $userId = $params['id'];
        #6A9955">// Use the ID to fetch from the database
        echo "Displaying profile for user ID: " . htmlspecialchars($userId);
    }
}

By passing $params from the Router to the Controller, you keep your logic decoupled. The controller doesn't need to know how the URL was parsed; it only cares that it received a valid ID.

Hands-on Exercise

  1. Modify your existing Router::add method to support the regex conversion logic shown above.
  2. Add a new route /product/{slug:[a-z0-9-]+}.
  3. Update your ProductController to accept the slug parameter and display it on the screen.
  4. Verify that hitting /product/my-cool-shirt works, but /product/123 fails (due to the regex constraint).

Common Pitfalls

  • Forgetting the Delimiters: Regex in PHP requires delimiters (usually /). If you forget to escape forward slashes in your URL path, the regex engine will fail.
  • Overly Broad Regex: Using .* in your regex is dangerous. Always be as specific as possible (e.g., [0-9]+ for IDs or [a-z]+ for slugs) to prevent malicious URLs from matching unintended routes.
  • Parameter Collision: If you define two routes that match the same pattern, the first one registered in your code will always win. Keep your most specific routes defined before your catch-all routes.

FAQ

Q: Can I make parameters optional? A: Yes, you can modify the regex to make a segment optional, but it increases complexity. For beginners, it's often cleaner to define two separate routes—one with the parameter and one without.

Q: Should I validate the parameters in the router? A: The router should only verify the format (regex). Business logic validation (e.g., "does this user actually exist in the database?") belongs inside your controller or model.

Recap

Team members presenting a project in a modern office setting with a focus on collaboration.

You have evolved your routing from static string matching to powerful pattern-based routing. By using regex to define constraints and capturing those segments as named groups, you've enabled your application to handle dynamic URLs while keeping controller logic clean and type-aware. This is a massive step toward building a professional-grade MVC application.

Up next: Building a Simple Authentication System to protect your dynamic routes.

Similar Posts