← Routing & Handlers

ROUTING & HANDLERS FEATURE

Parameterized Route

Path parameters like {slug} are extracted and injected via setters — with regex validation at the router level.

Feature Guide

A quick orientation block that answers the essential questions: what this feature does, how it works, why it matters, and the key concepts behind it.

What this does

Path parameters like {slug} are extracted from the URL and injected into the payload DTO via setter methods.

How it works

The router uses regex requirements to constrain what each parameter matches. The PayloadHydrator calls setters on the payload DTO with the extracted values. Default values are used when a parameter is not present.

Why it matters

Typed path parameters with regex constraints prevent invalid data from reaching handler code. The handler can trust that $payload->slug is always a valid, matched value.

Key concepts

requirements
Regex patterns that constrain path parameter values at the routing level.
PayloadHydrator
Populates payload DTO properties by calling setter methods with request data.

Path Parameters

Mechanical Keyboard

The slug from the URL is already validated and injected into the payload before the handler runs.

/demo/routing/parameterized/keyboard Active route
$129.99 Price
Input Category
requirements defaults PayloadHydrator setter injection

Verified against Semitexa Ultimate 2026.09.19.1020

Parameterized Route

A path parameter is declared inline in the route pattern and constrained by requirements on the same access attribute. Nothing else registers it — no separate attribute on the property, no route file.

#[AsPublicPayload(
    path: '/demo/routing/parameterized/{slug}',
    methods: ['GET'],
    responseWith: DemoFeatureResource::class,
    produces: ['application/json', 'text/html'],
    requirements: ['slug' => '[a-z0-9-]+'],
    defaults: ['slug' => 'headphones'],
)]
class ParameterizedRoutePayload
{
    protected string $slug = '';

    public function getSlug(): string
    {
        return $this->slug;
    }

    public function setSlug(string $slug): void
    {
        $this->slug = $slug;
    }
}

That is Semitexa\Demo\Application\Payload\Request\Routing\ParameterizedRoutePayload, served live at /demo/routing/parameterized/{slug}.

How the value reaches the payload

PayloadHydrator reads the route pattern and requirements from the payload's access attribute — any attribute extending AbstractPayloadRoute, so this works the same for public, protected and service payloads. It matches the incoming path, then merges the extracted parameters with the request body and query data.

Each value is then applied by setter name. The key is converted from snake_case to camelCase and prefixed with set, so slug looks for setSlug() and a query key user_id looks for setUserId(). A setter that does not exist, or that does not take exactly one required argument, is skipped silently — the property keeps its declared default. This is why the example declares protected string $slug = '' rather than leaving it uninitialised: a typo in the setter name costs you the value, not an error.

There is no property-level attribute involved. If you are looking for something to put above $slug, there isn't one.

Requirements are enforced, not advisory

The regex decides whether the route matches at all. Against the route above:

Request Result
/demo/routing/parameterized/wireless-mouse 200
/demo/routing/parameterized/NOT_VALID 404 — uppercase and _ fail [a-z0-9-]+

A value that fails the constraint never reaches the handler, so handler code can treat $payload->getSlug() as already validated against that pattern. It is a routing constraint, not a validation message: the client gets a 404, not a field error. When you need an explanation instead of a miss, validate in the payload and let payload validation produce the response.

defaults does not make the segment optional

This is the part that surprises people. The route above declares defaults: ['slug' => 'headphones'], and yet:

Request Result
/demo/routing/parameterized 404
/demo/routing/parameterized/ 404

defaults supplies a value when the route is matched without that parameter — it does not make the {slug} segment optional in the pattern. If you want the bare path to work, declare it as its own route, or make the segment optional in the pattern itself.

Checking a live route

routes:show prints the compiled pattern, the payload, and the resolved handler and resource for any route — see routing debugging.

© Jeff Sickel: "Deleted code is debugged code."

Payload Trusted input boundary
<?phpdeclare(strict_types=1);namespace App\Application\Payload\Routing;use App\Application\Resource\Page\ProductPageResource;use Semitexa\Core\Attribute\AsPublicPayload;#[AsPublicPayload(    path: '/products/{slug}',    methods: ['GET'],    responseWith: ProductPageResource::class,    requirements: ['slug' => '[a-z0-9-]+'],)]final class ParameterizedRoutePayload{    protected string $slug = '';    public function setSlug(string $slug): void    {        $this->slug = $slug;    }    public function getSlug(): string    {        return $this->slug;    }}

How it works

The router uses regex requirements to constrain what each parameter matches. The PayloadHydrator calls setters on the payload DTO with the extracted values. Default values are used when a parameter is not present.

Why it matters

Typed path parameters with regex constraints prevent invalid data from reaching handler code. The handler can trust that $payload->slug is always a valid, matched value.

Key concepts

requirements
Regex patterns that constrain path parameter values at the routing level.
PayloadHydrator
Populates payload DTO properties by calling setter methods with request data.

Support Semitexa
Built for developers who prefer control over magic. Your support helps keep it fast, open, and evolving.

Donate via PayPal