Form
Ray.WebFormModule provides aspect-oriented web form validation powered by Aura.Input and Ray.Di. Form fields, validation rules, submitted values, and rendering helpers are collected in a single form class so the form is easy to test and change.
Installation
Install ray/web-form-module with Composer.
composer require ray/web-form-module
Install WebFormModule in your application module.
use Ray\Di\AbstractModule;
use Ray\WebFormModule\WebFormModule;
class AppModule extends AbstractModule
{
protected function configure()
{
$this->install(new WebFormModule());
}
}
The legacy Ray\WebFormModule\AuraInputModule class remains available as a thin subclass of WebFormModule for backward compatibility. New code should use WebFormModule.
Form Class
A self-initializing form class defines fields and validation rules in init(). If the form implements submit(), the returned values are used as submitted data. See Aura.Input self-initializing forms for the underlying form API.
use Ray\WebFormModule\AbstractForm;
use Ray\WebFormModule\SetAntiCsrfTrait;
class MyForm extends AbstractForm
{
use SetAntiCsrfTrait;
public function init()
{
$this->setField('name', 'text')
->setAttribs([
'id' => 'name'
]);
$this->filter->validate('name')->is('alnum');
$this->filter->useFieldMessage('name', 'Name must be alphanumeric only.');
}
public function submit()
{
return $_POST;
}
public function __toString()
{
$form = $this->form();
$form .= $this->helper->tag('div', ['class' => 'form-group']);
$form .= $this->helper->tag('label', ['for' => 'name']);
$form .= 'Name:';
$form .= $this->helper->tag('/label') . PHP_EOL;
$form .= $this->input('name');
$form .= $this->error('name');
$form .= $this->helper->tag('/div') . PHP_EOL;
$form .= $this->input('submit');
$form .= $this->helper->tag('/form');
return $form;
}
}
Controller
Annotate methods that require form validation with #[FormValidation]. The form argument names the form property on the controller, and onFailure names the method called when validation fails.
use Ray\Di\Di\Inject;
use Ray\Di\Di\Named;
use Ray\WebFormModule\Annotation\FormValidation;
use Ray\WebFormModule\FormInterface;
class MyController
{
/** @var FormInterface */
protected $contactForm;
#[Inject]
public function setForm(#[Named("contact_form")] FormInterface $form)
{
$this->contactForm = $form;
}
#[FormValidation(form: "contactForm", onFailure: "badRequestAction")]
public function createAction()
{
// validation success
// More detail for vnd.error+json can be added with #[VndError].
}
public function badRequestAction()
{
// validation failed
}
}
View
When the form provides string rendering, echoing the form renders the complete HTML.
echo $form;
You can also render individual inputs and errors.
echo $form->input('name'); // <input id="name" type="text" name="name" size="20" maxlength="20" />
echo $form->error('name'); // "Name must be alphanumeric only." or blank.
CSRF Protections
CSRF protection is opt-in and can be enabled through either of two independent paths:
- Per-form: add
use SetAntiCsrfTrait;to the form.AntiCsrfInterfaceis injected at construction time, the token field is added inpostConstruct(), and everyapply()call verifies the token. - Per-action: annotate the validated method with
#[CsrfProtection].AuraInputInterceptorthen injectsAntiCsrfInterfaceinto the form beforeapply()runs.
Either path causes AbstractForm::apply() to throw CsrfViolationException on token mismatch. Without either path, no CSRF check is performed.
use Ray\WebFormModule\AbstractForm;
use Ray\WebFormModule\Annotation\CsrfProtection;
use Ray\WebFormModule\Annotation\FormValidation;
use Ray\WebFormModule\SetAntiCsrfTrait;
class MyForm extends AbstractForm
{
use SetAntiCsrfTrait;
}
class MyController
{
#[FormValidation(form: "contactForm")]
#[CsrfProtection]
public function createAction()
{
}
}
You can provide a custom AntiCsrf class. See Applying CSRF Protections in Aura.Input for details.
Migration From 0.x
Version 1.0 drops Doctrine Annotations in favor of native PHP 8 attributes and tightens type declarations. The most common rewrites are:
| Before (0.x) | After (1.0) |
|---|---|
@FormValidation(form="f", onFailure="badRequest") |
#[FormValidation(form: 'f', onFailure: 'badRequest')] |
@FormValidation(form="f", antiCsrf=true) |
#[FormValidation(form: 'f')] + #[CsrfProtection] |
@InputValidation(form="f") |
#[InputValidation(form: 'f')] |
@VndError(message="...", logref="...") |
#[VndError(message: '...', logref: '...')] |
new AuraInputInterceptor($injector, $reader) |
new AuraInputInterceptor($injector) |
public function input($input) / public function error($input) |
input(string $input): string / error(string $input): string |
See CHANGELOG.md for the full list of breaking changes.
Automated Migration With Claude Code
Ray.WebFormModule ships a Claude Code skill at .claude/skills/migrate-to-1.0/SKILL.md that walks an AI assistant through the rewrites above: annotations to attributes, antiCsrf=true split into #[CsrfProtection], Reader argument removal, and FormInterface signature updates. Copy that directory into your consuming project’s .claude/skills/ and invoke it with /migrate-to-1.0.
Validation Exception
#[InputValidation] throws Ray\WebFormModule\Exception\ValidationException when validation fails. This is useful for API applications where the HTML representation is not used.
use Ray\WebFormModule\Annotation\InputValidation;
class Foo
{
#[InputValidation(form: "form1")]
public function createAction($name)
{
// ...
}
}
Installing Ray\WebFormModule\FormVndErrorModule makes methods annotated with #[FormValidation] throw the same validation exception on failure.
use Ray\Di\AbstractModule;
use Ray\WebFormModule\FormVndErrorModule;
use Ray\WebFormModule\WebFormModule;
class FakeVndErrorModule extends AbstractModule
{
protected function configure()
{
$this->install(new WebFormModule());
$this->override(new FormVndErrorModule());
}
}
Echo the caught exception’s error property to get an application/vnd.error+json representation.
echo $e->error;
//{
// "message": "Validation failed",
// "path": "/path/to/error",
// "validation_messages": {
// "name": [
// "Name must be alphanumeric only."
// ]
// }
//}
Add more detail to vnd.error+json with the #[VndError] attribute.
#[FormValidation(form: "contactForm")]
#[VndError(message: "foo validation failed", logref: "a1000", path: "/path/to/error", href: ["_self" => "/path/to/error", "help" => "/path/to/help"])]
public function createAction()
{
}
Demo
Run the demo application from the Ray.WebFormModule repository.
php -S docs/demo/1.csrf/web.php