Derafu Form
A form is data, not HTML
Most form libraries tie the definition of a form to the HTML that draws it. In Derafu Form a form is plain data: a JSON Schema that says what the fields are, and a UI schema, in the style of JSON Forms, that says how they are laid out.
The definition is rendered on the server and validated by Derafu Data Processor when the request comes back. Because it follows JSON Forms, a simple form can also be drawn in the browser by that library, but Derafu Form adds features of its own that JSON Forms cannot draw. The validation always stays on the server.
How it works
The data, the layout, the page and the request: one definition for all of them.
Describe the data
Describe the layout
Render it
Process the request
See it working
Live forms from the documentation. Open one and use it.
One definition, the whole loop
From the definition to the page, and back from the request.
// A form is data. Three parts:
// schema what the data is: types, formats, which fields are required
// uischema how it is laid out
// data the values it starts with
$definition = [
'schema' => [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string', 'title' => 'Full Name'],
'email' => ['type' => 'string', 'format' => 'email', 'title' => 'Email Address'],
],
'required' => ['name', 'email'],
],
'uischema' => [
'type' => 'VerticalLayout',
'elements' => [
['type' => 'Control', 'scope' => '#/properties/name'],
['type' => 'Control', 'scope' => '#/properties/email'],
],
],
'data' => ['name' => '', 'email' => ''],
];
$form = $formFactory->create($definition);
{# The whole form, in one call. #}
{{ form(form) }}
{# Or row by row, when you need control over the page. #}
{{ form_start(form) }}
{{ form_row(form.fields.name) }}
{{ form_row(form.fields.email) }}
<button type="submit" class="btn btn-primary">Submit</button>
{{ form_end(form) }}
{# Options such as floating labels are given when rendering, to the form, or to one field. #}
{{ form(form, {floating_labels: true}) }}
// The data of the request goes through four phases, always in this order:
// cast, transform, sanitize and validate.
$result = $processor->process($form, $_POST);
if ($result->hasErrors()) {
$result->getErrors(); // ['email' => ['...the message...']]
$result->getForm(); // The form with its errors, to draw it again.
} else {
$result->getProcessedData(); // The data, cast and clean, ready to use.
}
// And a typed reader over the processed data, for nested values:
$result->getReader()->requireString('contact.address');
// The rules of a field come from three places, joined in this order:
// 1. the schema ('type', 'format', 'pattern', limits, 'enum')
// 2. the UI schema (a file control validates a file, for example)
// 3. the 'rules' of the definition, when you need something more:
'rules' => [
'name' => [
'transform' => ['uppercase'],
'validate' => ['min_length:3'],
],
],
// The same engine, Derafu Data Processor, also works alone, on any value:
$result = $dataProcessor->process(' [email protected] ', [
'transform' => ['lowercase'],
'sanitize' => ['trim'],
'validate' => ['email', 'max_length:255'],
]);
// Files and arrays too:
$dataProcessor->process($_FILES['upload'], [
'validate' => ['file:2M', 'mimetype:application/pdf,image/*'],
]);
// A simple form can be drawn in the browser by JSON Forms, with the same definition.
// Derafu Form adds features that JSON Forms cannot draw: a form that uses them
// works on the server only.
// It is optional: the server renders the form without any JavaScript.
import './jsonforms-viewer.js';
const formDefinition = {{ jsonFormsDefinition | json_encode | raw }};
const formInstance = window.renderJsonForm('json-form-container', formDefinition);
// formInstance gives you the data and the errors of the form.
# CSRF: one token for each form. The form draws it and checks it when it is processed.
# It needs the session of the request and one middleware. In a Symfony application,
# the bundle uses the CSRF manager of the application instead.
# Captcha: you choose the provider with environment variables.
CAPTCHA_PROVIDER=altcha # hcaptcha, turnstile, recaptcha-v3, altcha or none
CAPTCHA_SITE_KEY=... # the public key (altcha does not use it)
CAPTCHA_SECRET_KEY=... # the secret key (altcha: any long random text)
# altcha: the browser solves a proof of work. No third party and no account.
# A provider with its keys missing is a configuration error with a message that
# says what to fix. It is never a captcha that fails silently.
Frequently asked questions
What it does, and what it leaves to you.
required attribute on the required fields, as a help, but it is never what decides.
form, form_row, form_widget and more), and a Twig environment keeps one function for each name. The Symfony bundle solves it: when a template is compiled, each call to one of those functions becomes a choice made when the page is rendered, by its first argument. A Derafu form or field is drawn by Derafu, and a Symfony FormView is drawn by Symfony, with its own themes. You do not need prefixes. Symfony Form has to be enabled only if you use it.
resources/forms/ (also inside your bundles), loaded by name; they are rendered in the Twig environment of the application, where your templates in templates/form/ take precedence; the messages are translated by the Symfony translator; the JavaScript widgets are added to the import map of AssetMapper; and, if the application has the CSRF manager of Symfony, the tokens of the forms are generated and checked by it.
intl extension for the format rules of Data Processor. The Derafu packages are published as dev-main for now.
Where it fits
Other good tools exist. Choose by how you work.
| Approach | Examples | Choose it when | Derafu Form instead |
|---|---|---|---|
| The form library of a framework | Symfony Form, Laminas Form, Nette Forms | You want the ecosystem of that framework: its types, extensions and transformers. | A form is data, not code, so it needs no framework, and the layout and the validation are part of the definition. |
| HTML from a JSON Schema | kingsquare/json-schema-form, schema-forms (Drupal) | You only need a basic form from a schema, or you are in Drupal. | Layouts, tabs and conditional fields in the UI schema, with the validation and the errors of the request included. |
| A JSON Schema from your forms | Liform (Symfony) | Your forms already exist in Symfony and a JavaScript client needs their schema. | It goes the other way: the schema is the source, and the form comes from it. |
Install it
The form comes with its validation. CSRF and captcha are added when you need them.
# The Derafu packages are published as dev-main for now, so your composer.json needs:
# "minimum-stability": "dev", "prefer-stable": true
composer require derafu/form # includes derafu/data-processor
# Protection, optional:
composer require derafu/csrf derafu/captcha
# In a Symfony application:
composer require derafu/symfony-form-bundle
Next: the Form documentation, the Data Processor documentation and the Symfony bundle documentation explain the rest.
Ready to define a form once?
Open one of the live examples, then write your own definition.