Formuláre a POST spracovanie
Bežný tok od GET formulára cez POST, CSRF a validáciu až po redirect a nadväzujúci GET.
Kompletný tok formulára
Bežný server-rendered flow vyzerá takto: GET vyrenderuje formulár, POST prejde cez CsrfMiddleware, controller načíta vstup a raz ho zvaliduje. Pri chybe vráti formulár s chybami a pôvodnými hodnotami, pri úspechu odovzdá zápis aplikačnej službe, uloží flash správu, vykoná redirect a skončí nadväzujúcim GET. To je Post/Redirect/Get pattern. Pre detailnejší návrh validačných pravidiel pokračuj na Validáciu a pre ďalšie HTTP guardy na Middleware.
Routy a ochrana CSRF
GET route nechaj jednoduchú a CsrfMiddleware pripoj k POST route, ktorá mení stav. View vyrenderuje token cez vstavaný CSRF helper, takže request aj view používajú rovnaký mechanismus frameworku.
<?php
use App\Controllers\ContactController;
use Lemonade\Framework\Routing\Router;
use Lemonade\Framework\Security\Csrf\CsrfMiddleware;
return static function (Router $router): void {
$router->getNamed('contact.form', '/contact', ContactController::class . '@form');
$router
->postNamed('contact.submit', '/contact', ContactController::class . '@submit')
->middleware(CsrfMiddleware::class);
};GET akcia, POST akcia, validácia a redirect
GET akcia pripraví východiskové hodnoty a vyrenderuje stránku. POST akcia načíta vstup cez controller helpery, raz ho zvaliduje, pri chybe vráti rovnaký formulár s HTTP 422 a pri úspechu zavolá aplikačnú službu pre zápis, nastaví flash správu a vykoná redirect.
<?php
declare(strict_types=1);
namespace App\Controllers;
use App\Services\ContactSubmissionService;
use Lemonade\Framework\Core\AbstractController;
use Lemonade\Framework\Validation\ValidationSchema;
use Psr\Http\Message\ResponseInterface;
final class ContactController extends AbstractController
{
public function __construct(
private readonly ContactSubmissionService $contactService,
) {}
public function form(): ResponseInterface
{
return $this->renderForm(
values: [
'name' => '',
'email' => '',
'message' => '',
],
errors: [],
);
}
public function submit(): ResponseInterface
{
$values = [
'name' => $this->inputString('name'),
'email' => $this->inputString('email'),
'message' => $this->inputString('message'),
];
$schema = ValidationSchema::create()
->field('name', 'Name')
->required()
->maxLength(100)
->end()
->field('email', 'E-mail')
->required()
->email()
->end()
->field('message', 'Message')
->required()
->maxLength(2000)
->end();
$result = $this->validator()->validate($values, $schema);
if (!$result->isValid()) {
return $this->renderForm(
values: $values,
errors: $result->errors(),
status: 422,
);
}
$this->contactService->store($result->validated());
$this->flash()->set('success', 'Your message has been sent.');
return $this->redirect($this->url()->route('contact.form'));
}
/**
* @param array{name:string,email:string,message:string} $values
* @param array<string, string> $errors
*/
private function renderForm(array $values, array $errors, int $status = 200): ResponseInterface
{
return $this->html(
$this->view()->template('layouts.app', 'pages.contact-form', [
'values' => $values,
'errors' => $errors,
]),
$status,
);
}
}Vyrenderuj formulár s CSRF, hodnotami a chybami
Form view vyrenderuje csrfField(), vráti odoslané hodnoty späť do inputov a na redirectovanom GET prečíta success flash správu. values a errors odovzdávaj z controlleru explicitne, aby view nezáviselo od skrytých helperov demo aplikácie.
<?php
/**
* @var \Lemonade\Framework\View\View $this
* @var \Lemonade\Framework\View\ViewHelpers $helpers
* @var \Lemonade\Framework\View\RequestViewHelpers $requestHelpers
* @var array<string, string> $errors
* @var array{name:string,email:string,message:string} $values
*/
?>
<?php if ($success = $requestHelpers->flash('success')): ?>
<div class="alert alert-success"><?= e((string) $success) ?></div>
<?php endif; ?>
<form method="post" action="<?= e($helpers->url('contact.submit')) ?>">
<?= $helpers->csrfField() ?>
<input name="name" value="<?= e($values['name']) ?>">
<input name="email" value="<?= e($values['email']) ?>">
<textarea name="message"><?= e($values['message']) ?></textarea>
<?php if (isset($errors['message'])): ?>
<p><?= e($errors['message']) ?></p>
<?php endif; ?>
<button type="submit">Send</button>
</form>Zápis drž mimo controller
Controller má riešiť HTTP orchestration. Samotný zápis môže žiť v malej aplikačnej službe, keď už nejde iba o jednoduché prepojenie requestu s frameworkom. Controller tak zostane čitateľný bez toho, aby sa service alebo repository vrstva stala povinnosťou.
<?php
declare(strict_types=1);
namespace App\Services;
use Lemonade\Framework\Database\Database;
final class ContactSubmissionService
{
public function __construct(
private readonly Database $db,
) {}
/**
* @param array{name:string,email:string,message:string} $data
*/
public function store(array $data): void
{
$this->db->table('contact_messages')->insert($data);
}
}