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.

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

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.
PHPclass 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:
PHPclass 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
- Modify your existing
Router::addmethod to support the regex conversion logic shown above. - Add a new route
/product/{slug:[a-z0-9-]+}. - Update your
ProductControllerto accept theslugparameter and display it on the screen. - Verify that hitting
/product/my-cool-shirtworks, but/product/123fails (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

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.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

FilamentPHP Admin Panel & Dashboard Development
A powerful admin panel for your Laravel app — built with FilamentPHP so you can manage everything without touching the database.