An aspect oriented web form module powered by Aura.Input and Ray.Di.
$ composer require ray/web-form-module
useRay\Di\AbstractModule;
useRay\WebFormModule\WebFormModule;
class AppModule extends AbstractModule
{
protectedfunctionconfigure()
{
$this->install(newWebFormModule());
}
}The legacy
Ray\WebFormModule\AuraInputModuleclass is still available as a thin subclass ofWebFormModulefor backwards compatibility. New code should preferWebFormModule.
We provide two methods on self-initializing form class, one is init() method where we add an input field on form and apply fileters and rules. The other method method is submit() where it submit data. See more detail at Aura.Input self-initializing forms.
useRay\WebFormModule\AbstractForm;
useRay\WebFormModule\SetAntiCsrfTrait;
class MyForm extends AbstractForm
{
// for anti CSRFuse SetAntiCsrfTrait;
/** * {@inheritdoc} */publicfunctioninit()
{
$this->setField('name', 'text')
->setAttribs([
'id' => 'name'
]);
$this->filter->validate('name')->is('alnum');
$this->filter->useFieldMessage('name', 'Name must be alphabetic only.');
}
/** * {@inheritdoc} */publicfunctionsubmit()
{
return$_POST;
}
/** * {@inheritdoc} */publicfunction__toString()
{
$form = $this->form();
// name$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;
// submit$form .= $this->input('submit');
$form .= $this->helper->tag('/form');
return$form;
}
}We annotate the methods which web form validation is required with #[FormValidation]. We can specify form object property name with form and failure method name with onFailure.
useRay\Di\Di\Inject;
useRay\Di\Di\Named;
useRay\WebFormModule\Annotation\FormValidation;
useRay\WebFormModule\FormInterface;
class MyController
{
/** * @var FormInterface */protected$contactForm;
#[Inject]
publicfunctionsetForm(#[Named("contact_form")] FormInterface$form)
{
$this->contactForm = $form;
}
#[FormValidation(form: "contactForm", onFailure: "badRequestAction")]
publicfunctioncreateAction()
{
// validation success// More detail for `vnd.error+json` can be added with `#[VndError]`.
}
publicfunctionbadRequestAction()
{
// validation faild
}
}You can render entire form html when __toString is given.
echo$form; // render entire form htmlor render input element basis.
echo$form->input('name'); // <input id="name" type="text" name="name" size="20" maxlength="20" />echo$form->error('name'); // "Name must be alphabetic only." or blank.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 by Ray.Di through the trait's#[Inject]setter, the token field is added inpostConstruct(), and everyapply()call verifies the token. - Per-action: annotate the validated controller 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. Combining
both paths is harmless but redundant — pick whichever fits your use case.
Per-action — declare CSRF on the controller method:
useRay\WebFormModule\Annotation\CsrfProtection;
useRay\WebFormModule\Annotation\FormValidation;
class MyController
{
#[FormValidation(form: "contactForm")]
#[CsrfProtection]
publicfunctioncreateAction()
{
}
}Per-form — declare CSRF on the form itself:
useRay\WebFormModule\AbstractForm;
useRay\WebFormModule\SetAntiCsrfTrait;
class MyForm extends AbstractForm
{
use SetAntiCsrfTrait;
}You can provide your custom AntiCsrf class. See more detail at Aura.Input
Version 1.0 drops Doctrine Annotations in favour of native PHP 8 Attributes and tightens type declarations. The most common rewrites:
| 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) (no Reader argument) |
public function input($input) / public function error($input) | public function input(string $input): string / error(string $input): string |
See CHANGELOG.md for the full list of breaking changes.
The repository ships a Claude Code skill at
.claude/skills/migrate-to-1.0/SKILL.md
that walks an AI assistant through the rewrites above (annotations →
attributes, antiCsrf=true split into #[CsrfProtection], Reader
argument removal, FormInterface signature updates). Copy the directory
into your consuming project's .claude/skills/ and invoke it via
/migrate-to-1.0.
When we install Ray\WebFormModule\FormVndErrorModule as following,
useRay\Di\AbstractModule;
class FakeVndErrorModule extends AbstractModule
{
protectedfunctionconfigure()
{
$this->install(newWebFormModule());
$this->override(newFormVndErrorModule());
}A Ray\WebFormModule\Exception\ValidationException will be thrown.
We can echo catched exception to get application/vnd.error+json media type.
echo$e->error;
//{// "message": "Validation failed",// "path": "/path/to/error",// "validation_messages": {// "name": [// "Name must be alphabetic only."// ]// }//}More detail for vnd.error+json can be added 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"])]This optional module is handy for API application.
$ php -S docs/demo/1.csrf/web.php
