Forms

A form is a RequestInput with its rules in ValidatableInput: a typed object mapped from the query string and the body. See Request input.

use Kaly\Http\Input\ValidatableInput;
use Kaly\Validation\Validator;

final readonly class CheckoutInput implements ValidatableInput
{
    public function __construct(
        public string $email,
        public int $quantity = 1,
    ) {}

    public function validate(Validator $v): void
    {
        $v->email('email', $this->email);
        $v->between('quantity', $this->quantity, 1, 10);
    }
}

A JSON action declares the input and lets the dispatcher throw: a ValidationException becomes a 422 problem+json response with an errors list.

public function checkout(CheckoutInput $input): never
{
    $order = $this->orders->place($input->email, $input->quantity);
    $this->redirectToRoute('shop:confirmation', ['id' => $order->id]);
}

An HTML action re-renders instead, keeping exactly what was sent plus the violations (422) rather than redirecting: a redirect would lose what the user typed.

use Kaly\Http\Input\InputMapperInterface;

final class ShopController extends AbstractController
{
    public function __construct(
        ServerRequestInterface $request,
        private InputMapperInterface $inputs,
    ) {
        parent::__construct($request);
    }

    public function checkoutForm(): View
    {
        $result = $this->inputs->mapResult($this->request, CheckoutInput::class);

        if (!$result->isValid()) {
            return new View('shop/checkout', [
                'values' => $result->values(),
                'errors' => $result->validation(),
            ], status: $result->status());
        }

        // require() returns CheckoutInput: the result carries the DTO type
        $input = $result->require();

        $order = $this->orders->place($input->email, $input->quantity);
        $this->redirectToRoute('shop:confirmation', ['id' => $order->id]);
    }
}

From a test, both encodings work through the same action:

$client->post('/shop/checkout/', ['form' => ['email' => 'ada@example.test']])
    ->assertStatus(303);

$client->post('/shop/checkout/', ['json' => ['email' => 'ada@example.test']])
    ->assertStatus(303);

Generate the form action with $url('shop:checkout') so templates never hardcode paths. See Views and Testing.

For rich server-rendered form presentation and progressive enhancement, the companion kaly-forms library builds on these values and violations. Kaly’s native forms cover request input and validation; kaly-forms adds the rendering and interaction layer.


Kaly PHP framework — MIT

This site uses Just the Docs, a documentation theme for Jekyll.