← Routing & Handlers

ROUTING & HANDLERS FEATURE

Payload As A Shield

A payload is the one trusted boundary: external data is normalized inside setters before application code runs.

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

Payloads are the shield from external data: hydration happens first, and each setter owns the normalization and guard logic for its own field before the handler runs.

How it works

PayloadHydrator maps request input into the payload via typed setters. Each setter can normalize its value and throw a field-aware ValidationException when the input is invalid, which keeps the boundary close to the field itself.

Why it matters

This keeps the transport boundary explicit without forcing one DTO-wide validation method to know every field. The payload owns the input truth, the handler owns the use case, and additional fields can be added by payload parts without reopening a central validate() method.

Key concepts

PayloadHydrator
Hydrates payload DTOs from HTTP input by calling typed setters.
ValidationException
Field-aware exception that a setter can throw when the incoming value is not acceptable.
setter-owned validation
The field that owns the data also owns its normalization and guard logic.

Trusted Input Boundary

Payloads turn raw HTTP into application-grade data

The payload is not a passive DTO. It is the shield between external input and the use case: hydration, type casting, and validation happen first, and only then does business code run.

What usually goes wrong

  • Raw request arrays make controllers mix transport parsing, validation, and business rules in one method.
  • The same checks get repeated across handlers because there is no single boundary object that owns input truth.
  • When invalid data slips through, the handler must keep defending itself instead of focusing on the business action.
1 trusted boundary object
422 automatic invalid-input rejection
0 transport checks left in the handler

Step 1

Hydrate

PayloadHydrator maps request input into one payload object via typed setters.

Step 2

Normalize

Setter code trims, casts, and shapes the input for the field it owns.

Step 3

Guard

Setter-level checks throw a field-aware ValidationException before invalid data can reach the handler.

Step 4

Handle

Business code receives a trusted DTO and can focus on intent, not defensive parsing.

Scattered Responsibility

Raw request in the handler

Input extraction, branching, validation errors, and business rules all compete in one controller method.

The boundary is blurry, so every handler keeps re-checking input just in case.

Single Source Of Truth

Payload as the shield

The payload owns hydration and field guards, so the handler receives clean data it can trust.

Single responsibility becomes obvious: setters guard input, handler executes the use case.

PayloadHydrator ValidationException setter guards 422 before handler

Verified against Semitexa Ultimate 2026.09.19.1020

Payload As A Shield

Payloads are the shield from external data: hydration happens first, and each setter owns the normalization and guard logic for its own field before the handler runs.

How it works

PayloadHydrator maps request input into the payload via typed setters. Each setter can normalize its value and throw a field-aware ValidationException when the input is invalid, which keeps the boundary close to the field itself.

Why this matters

This keeps the transport boundary explicit without forcing one DTO-wide validation method to know every field. The payload owns the input truth, the handler owns the use case, and additional fields can be added by payload parts without reopening a central validate() method.

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

Typical Controller Implementation slice
<?phpdeclare(strict_types=1);final class LegacyCheckoutController{    public function create(Request $request): Response    {        $email = trim((string) $request->input('email', ''));        $coupon = trim((string) $request->input('coupon', ''));        $agreeToTerms = (bool) $request->input('agree_to_terms', false);        if ($email === '') {            return Response::json(['errors' => ['email' => ['Email is required.']]], 422);        }        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {            return Response::json(['errors' => ['email' => ['Email is invalid.']]], 422);        }        if ($agreeToTerms !== true) {            return Response::json(['errors' => ['agree_to_terms' => ['Terms must be accepted.']]], 422);        }        return $this->checkoutService->create(            email: $email,            coupon: $coupon !== '' ? strtoupper($coupon) : null,            agreeToTerms: $agreeToTerms,        );    }}

Single Responsibility

One place to trust, one place to act

This is the architectural payoff: payloads own the external boundary, handlers own the use case. The split is visible in code review and stays stable across the whole application.

The payload is the single place where external data becomes internal application data.

Setter signatures define the accepted shape, and setter code defines the accepted business constraints.

If the payload is invalid, the request ends before the handler is called.

Handlers should read like use cases, not like defensive transport parsers.

Boundary concern What Semitexa does with it
Hydration PayloadHydrator calls typed setters on the payload DTO.
Field guards Setters throw field-aware exceptions when incoming values are not acceptable.
Trust boundary Once the handler runs, the payload should already be safe to consume.

How it works

PayloadHydrator maps request input into the payload via typed setters. Each setter can normalize its value and throw a field-aware ValidationException when the input is invalid, which keeps the boundary close to the field itself.

Why it matters

This keeps the transport boundary explicit without forcing one DTO-wide validation method to know every field. The payload owns the input truth, the handler owns the use case, and additional fields can be added by payload parts without reopening a central validate() method.

Key concepts

PayloadHydrator
Hydrates payload DTOs from HTTP input by calling typed setters.
ValidationException
Field-aware exception that a setter can throw when the incoming value is not acceptable.
setter-owned validation
The field that owns the data also owns its normalization and guard logic.

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

Donate via PayPal