PHP Luminova: Dynamic URI Routing
Create dynamic routes in Luminova using predefined placeholders, named placeholders, or raw regular expressions. Learn how URI segments are matched, validated, and passed to controller methods.
Dynamic URIs are parts of a route path that change per request. The routing system parses these segments and passes the resolved values to the controller method as arguments, similar to Dependency Injection.
Luminova supports dynamic segments in three ways:
Raw regular expressions
- Most flexible, but hard to read and maintain
- Example:
/blog/([a-zA-Z0-9]+)/([0-9]+)
Named placeholders
- Clear and readable, but no built-in type validation
- Example:
/blog/{title}/{id}
Predefined placeholders
- Clean syntax with strict type validation
- Example:
/blog/(:string)/(:int) - See Dynamic URI Placeholders documentation.
Raw regex gives full control, but named and predefined placeholders are safer and easier to maintain.Use them when you want clear, consistent routes without dealing with complex patterns.
Using Predefined Placeholders
Predefined placeholders allows you to create dynamic routes without writing regular expressions.They make routes easy to read, easy to maintain, and safe, because each placeholder only matches the expected type.
Each placeholder represents one part of the URL and is automatically validated before the controller method is called.
Attribute-Based Routing
Attribute-based routing keeps the route definition close to the controller logic.This makes it easier to understand what URLs a method responds to.
// /app/Controllers/Http/WebController.php
namespace App\Controllers\Http;
use Luminova\Base\Controller;
use Luminova\Attributes\Route;
class WebController extends Controller
{
#[Route('/user/(:int)/(:username)', methods: ['GET'])]
public function user(int $id, string $username): int
{
// Matches: GET /user/42/john-doe
// (:int) → $id (must be an integer)
// (:username) → $username (letters, numbers, dots, dashes)
}
#[Route('/file/(:number)', methods: ['GET'])]
public function file(string $version): int
{
// Matches: GET /file/1.5
// (:number) → $version (integer or decimal)
}
}What happens here:
- The routing system checks each URL segment against the placeholder
- If validation passes, values are passed to the method as arguments
- If validation fails, the route is not matched
Method-Based Routing
Method-based routing defines routes in a single file.This approach is familiar, simple, and works well for smaller projects or teams that prefer centralized routing.
// /routes/web.php
Router::get('/user/(:int)/(:username)', 'WebController::user');
Router::get('/file/(:number)', 'WebController::file');How it works:
- The route pattern defines what the URL must look like
- Placeholders enforce the expected data type
- Matched values are passed to the controller method automatically
Here’s a tightened, beginner-friendly version with clearer warnings and plain explanations.
Using Named Placeholders
Named placeholders allows you to define dynamic route segments using readable names instead of patterns.They make routes easy to understand at a glance, but they do not enforce any validation.
In simple terms:
If the URL fits the shape, it matches, even if the value makes no sense.
Use named placeholders when readability and speed matter more than strict input checks.
Named Attribute-Based Routing
This style keeps the route close to the controller method, making it easy to see which URLs call which logic.
// /app/Controllers/Http/WebController.php
namespace App\Controllers\Http;
use Luminova\Base\Controller;
use Luminova\Attributes\Route;
class WebController extends Controller
{
#[Route('/user/{id}/{username}', methods: ['GET'])]
public function user(int $id, string $username): int
{
// Matches: GET /user/42/john-doe
// Also matches: /user/abc/???
// No type checks are done at the routing level
}
#[Route('/file/{version}', methods: ['GET'])]
public function file(string $version): int
{
// Matches: GET /file/1.5
// Also matches: /file/anything-here
}
}Important Note:
{id}and{username}match any text- PHP type hints do not stop the route from matching
- You must manually validate values inside the method if needed
Named Method-Based Routing
This approach defines routes in a central file.It’s straightforward and familiar, especially for developers coming from older frameworks.
// /routes/web.php
Router::get('/user/{id}/{username}', 'WebController::user');
Router::get('/file/{version}', 'WebController::file');How this behaves:
- Any value is accepted for named placeholders
- The route matches first, validation happens later (if you add it)
Note
Named placeholders do not validate input types.If you need strict matching (for example, numbers only),use predefined placeholders like
(:int)or(:number)instead.
Using Raw Regular Expressions
Raw regular expressions give you full control over how a route matches a URL.They work the same way as predefined placeholders, but you must write and maintain the patterns yourself.
This approach is powerful, but also harder to read, harder to debug, and easier to break.
Use raw regex only when placeholders cannot express what you need.
Regex Attribute-Based Routing
Routes are defined directly on the controller methods using full regular expressions.
// /app/Controllers/Http/WebController.php
namespace App\Controllers\Http;
use Luminova\Base\Controller;
use Luminova\Attributes\Route;
class WebController extends Controller
{
#[Route('/user/(\d+)/([a-zA-Z0-9._-]+)', methods: ['GET'])]
public function user(int $id, string $username): int
{
// Matches: GET /user/42/john-doe
// (\d+) → numeric user ID
// ([a-zA-Z0-9._-]+) → username
}
#[Route('/file/([+-]?\d+(?:\.\d+)?)', methods: ['GET'])]
public function file(string $version): int
{
// Matches: GET /file/1.5
// Accepts integers or decimal numbers
}
}Regex Method-Based Routing
Routes can also be defined in a central routing file using raw expressions.
// /routes/web.php
Router::get('/user/(\d+)/([a-zA-Z0-9._-]+)', 'WebController::user');
Router::get('/file/([+-]?\d+(?:\.\d+)?)', 'WebController::file');Recommendation
Prefer predefined placeholders whenever possible.They are easier to read, easier to maintain, and reduce routing errors without sacrificing correctness.