Luminova Framework

PHP Luminova: Dynamic URI Routing

Last updated: 2026-01-29 16:49:04

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

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.