Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 89 additions & 61 deletions manuals/1.0/en/form.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,17 @@ permalink: /manuals/1.0/en/form.html

# Form

Each related function of Web Forms using [Aura.Input](https://github.com/auraphp/Aura.Input) and [Aura.Filter](https://github.com/auraphp/Aura.Filter) is aggregated to a single class so that it is easy to test and change.
We can use a corresponding class for the use of Web Forms and validation.
Web form handling powered by [Aura.Input](https://github.com/auraphp/Aura.Input) and [Aura.Filter](https://github.com/auraphp/Aura.Filter) aggregates related functionality into a single class, making it easy to test and modify. A single form class can serve both rendering and validation.

## Install

Install `ray/web-form-module` via composer to add form using Aura.Input
Install `ray/web-form-module` with composer:

```bash
composer require ray/web-form-module
```

Install `AuraInputModule` in our application module `src/Module/AppModule.php`
Install `WebFormModule` in your application module at `src/Module/AppModule.php`:

```php
use BEAR\Package\AbstractAppModule;
Expand All @@ -29,123 +28,144 @@ class AppModule extends AbstractAppModule
protected function configure()
{
// ...
$this->install(new AuraInputModule);
$this->install(new WebFormModule());
}
}
```

## Web Form
## Form Class

Create **a form class** that defines the registration and the rules of form elements, then bind it to a method using `#[FormValidation]` attribute.
The method runs only when the sent data is validated.
Create a **form class** that defines input elements and validation rules, then bind it to a method with the `#[FormValidation]` attribute. The method only runs when validation succeeds.

```php
use Ray\WebFormModule\AbstractForm;
use Ray\WebFormModule\SetAntiCsrfTrait;

class MyForm extends AbstractForm
{
/**
* {@inheritdoc}
*/
use SetAntiCsrfTrait;

public function init()
{
// set input fields
// register input fields
$this->setField('name', 'text')
->setAttribs([
'id' => 'name'
]);
// set rules and user defined error message
->setAttribs(['id' => 'name']);

// set validation rules and user-defined error messages
$this->filter->validate('name')->is('alnum');
$this->filter->useFieldMessage('name', 'Name must be alphabetic only.');
}
}
```

We can register the input elements in the `init()` method of the form class and apply the rules of validation and sanitation.
Please refer to [Rules To Validate Fields](https://github.com/auraphp/Aura.Filter/blob/2.x/docs/validate.md) of Aura.Filter with respect to validation rules, and [Rules To Sanitize Fields](https://github.com/auraphp/Aura.Filter/blob/2.x/docs/sanitize.md) with respect to sanitize rules.
Register input elements inside `init()` and apply validation and sanitization rules. See:

- [Rules To Validate Fields](https://github.com/auraphp/Aura.Filter/blob/2.x/docs/validate.md)
- [Rules To Sanitize Fields](https://github.com/auraphp/Aura.Filter/blob/2.x/docs/sanitize.md)

We validate an associative array of the argument of the method.
If we want to change the input, we can set the values by implementing `submit()` method of `SubmitInterface` interface.
The associative array of method arguments is validated. To change the input, implement `SubmitInterface::submit()` and return the values to use.

## #[FormValidation] Attribute

Annotate the method that we want to validate with the `#[FormValidation]`, so that the validation is done in the form object specified by the `form` property before execution.
When validation fails, the method with the `ValidationFailed` suffix is called.
A method annotated with `#[FormValidation]` is validated before execution against the form object referenced by the `form` property. When validation fails, the method whose name is suffixed with `ValidationFailed` is invoked instead:

```php
use Ray\Di\Di\Inject;
use BEAR\Resource\ResourceObject;
use Ray\Di\Di\Named;
use Ray\WebFormModule\Annotation\FormValidation;
use Ray\WebFormModule\FormInterface;

class MyController
class MyPage extends ResourceObject
{
protected FormInterface $form;

#[Inject]
#[Named('contact_form')]
public function setForm(FormInterface $form)
{
$this->form = $form;
public function __construct(
#[Named('contact_form')] private FormInterface $contactForm,
) {
}

#[FormValidation]
// or
// #[FormValidation(form: 'form', onFailure: 'onPostValidationFailed')]
public function onPost($name, $age)
#[FormValidation(form: 'contactForm')]
public function onPost(string $name, int $age): static
{
// validation success
return $this;
}

public function onPostValidationFailed($name, $age)
public function onPostValidationFailed(string $name, int $age): static
{
// validation failed
// validation failure
return $this;
}
}
```

We can explicitly specify the name and the method by changing the `form` property of `#[FormValidation]` attribute or the `onValidationFailed` property.
Use the `form` property of `#[FormValidation]` to point to a different form property, and `onFailure` to specify a custom failure-handler method name:

The submit parameters will be passed to the `onPostValidationFailed` method.
```php
#[FormValidation(form: 'contactForm', onFailure: 'badRequestAction')]
public function onPost(string $name, int $age): static
{
return $this;
}
```

The submitted arguments are forwarded to the failure-handler method.

## View

Specify the element name to get the `input` elements and error messages
Pass the element name to render the `input` markup or to fetch the error message:

```php
$form->input('name'); // <input id="name" type="text" name="name" size="20" maxlength="20" />
$form->error('name'); // "Please enter a double-byte characters or letters in the name." or blank
$form->input('name'); // e.g. <input id="name" type="text" name="name" size="20" maxlength="20" />
$form->error('name'); // e.g. "Name must be alphabetic only."
```

The same applies to Twig template
The same works in Twig templates:

```php
```twig
{% raw %}{{ form.input('name') }}
{{ form.error('name') }}{% endraw %}
```

If the form class implements `ToStringInterface`, the whole form can be rendered by casting it to string:

```php
echo $form; // render the entire form HTML
```

## CSRF Protections

We can add a CSRF(Cross site request forgeries) object to the form to apply CSRF protections.
CSRF protection is **opt-in**. A form that uses `SetAntiCsrfTrait` is wired with an `AntiCsrfInterface`, but the token is only verified on methods annotated with `#[CsrfProtection]`. Methods without `#[CsrfProtection]` perform no CSRF check even if the form has an `AntiCsrf` object set.

```php
use BEAR\Resource\ResourceObject;
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 MyPage extends ResourceObject
{
#[FormValidation(form: 'contactForm')]
#[CsrfProtection]
public function onPost(string $name, int $age): static
{
// executed only when the CSRF token is valid
return $this;
}
}
```

In order to increase the security level, add a custom CSRF class that contains the user authentication to the form class.
Please refer to the [Applying CSRF Protections](https://github.com/auraphp/Aura.Input#applying-csrf-protections) of Aura.Input for more information.
To raise the security level, provide a custom CSRF class that incorporates user authentication and set it on the form. See [Applying CSRF Protections](https://github.com/auraphp/Aura.Input#applying-csrf-protections) in Aura.Input for details.

## #[InputValidation] attribute
## #[InputValidation]

If we annotate the method with `#[InputValidation]` instead of `#[FormValidation]`, the exception `Ray\WebFormModule\Exception\ValidationException` is thrown when validation fails.
For convenience, HTML representation is not used in this case.
If a method is annotated with `#[InputValidation]` instead of `#[FormValidation]`, a `Ray\WebFormModule\Exception\ValidationException` is thrown when validation fails. No HTML representation is used, which is convenient for Web APIs.

When we `echo` the `error` property of the caught exception, we can see the representation of the media type [application/vnd.error+json](https://github.com/blongden/vnd.error).
`echo`ing the `error` property of the caught exception outputs an [application/vnd.error+json](https://github.com/blongden/vnd.error) representation:

```php
http_response_code(400);
Expand All @@ -156,13 +176,13 @@ echo $e->error;
// "path": "/path/to/error",
// "validation_messages": {
// "name": [
// "Please enter a double-byte characters or letters in the name."
// "Name must be alphabetic only."
// ]
// }
// }
```

We can add the necessary information to `vnd.error+json` using `#[VndError]` attribute.
Use the `#[VndError]` attribute to enrich the `vnd.error+json` payload:

```php
#[FormValidation(form: 'contactForm')]
Expand All @@ -172,27 +192,35 @@ We can add the necessary information to `vnd.error+json` using `#[VndError]` att
path: '/path/to/error',
href: ['_self' => '/path/to/error', 'help' => '/path/to/help']
)]
public function onPost()
public function onPost(): static
{
return $this;
}
```

## FormVndErrorModule

If we install `Ray\WebFormModule\FormVndErrorModule`, the method annotated with `#[FormValidation]`
will throw an exception in the same way as the method annotated with `#[InputValidation]`.
We can use the page resources as API.
Install `Ray\WebFormModule\FormVndErrorModule` to make `#[FormValidation]` methods throw the same exception as `#[InputValidation]` methods. Page resources can then be reused as API endpoints:

```php
use Ray\Di\AbstractModule;
use Ray\WebFormModule\WebFormModule;
use Ray\WebFormModule\FormVndErrorModule;

class FooModule extends AbstractModule
{
protected function configure()
{
$this->install(new AuraInputModule);
$this->override(new FormVndErrorModule);
$this->install(new WebFormModule());
$this->override(new FormVndErrorModule());
}
}
```

## Migration from 0.x

Version 1.0 drops Doctrine Annotations in favour of native PHP 8 Attributes and turns CSRF protection into opt-in via `#[CsrfProtection]`, among other breaking changes. See the [Ray.WebFormModule README](https://github.com/ray-di/Ray.WebFormModule#migration-from-0x) and the [CHANGELOG](https://github.com/ray-di/Ray.WebFormModule/blob/1.x/CHANGELOG.md) for the upgrade steps.

## Demo

Try the demo app [MyVendor.ContactForm](https://github.com/bearsunday/MyVendor.ContactForm) to get an idea on how forms such as
a confirmation form and multiple forms in a single page work.
Try the demo app [MyVendor.ContactForm](https://github.com/bearsunday/MyVendor.ContactForm) to see how a confirmation form and multiple forms on a single page work.
Loading