phil / forms-bundle
Forms defined in the database for Symfony: a page for each, protected against spam, questions asked under conditions or for some tiers, choices with places, entries kept no longer than needed, sensitive values sealed, sent by email or to a signed webhook
Package info
github.com/parler-haut-interagir-librement/forms-bundle
Type:symfony-bundle
pkg:composer/phil/forms-bundle
Requires
- php: >=8.5
- ext-filter: *
- ext-mbstring: *
- ext-sodium: *
- doctrine/dbal: ^4.5
- doctrine/orm: ^3.7
- phil/spam-protection-bundle: ^0.1@dev
- phil/standard-webhooks: ^0.1@dev
- psr/clock: ^1.0
- psr/log: ^3.0
- symfony/config: ^8.2
- symfony/dependency-injection: ^8.2
- symfony/form: ^8.2
- symfony/http-foundation: ^8.2
- symfony/http-kernel: ^8.2
- symfony/options-resolver: ^8.2
- symfony/uid: ^8.2
- twig/twig: ^3.21
Requires (Dev)
- ext-intl: *
- doctrine/doctrine-bundle: ^3.3
- doctrine/persistence: ^4.1
- phil/form-logic: ^0.1@dev
- phil/privacy-bundle: ^0.1@dev
- phil/spam-protection: ^0.1@dev
- php-webdriver/webdriver: ^1.16
- phpunit/phpunit: ^13.4
- symfony/asset-mapper: ^8.2
- symfony/clock: ^8.2
- symfony/console: ^8.2
- symfony/event-dispatcher: ^8.2
- symfony/framework-bundle: ^8.2
- symfony/http-client: ^8.2
- symfony/http-client-contracts: ^3.6
- symfony/key-management: ^8.2
- symfony/mailer: ^8.2
- symfony/mime: ^8.2
- symfony/panther: ^2.4
- symfony/routing: ^8.2
- symfony/security-core: ^8.2
- symfony/stimulus-bundle: ^3.7
- symfony/translation: ^8.2
- symfony/translation-contracts: ^3.6
- symfony/twig-bridge: ^8.2
- symfony/twig-bundle: ^8.2
- symfony/validator: ^8.2
- symfony/yaml: ^8.2
Suggests
- phil/form-logic: To show and hide the fields in the browser as the form is filled, and to read the conditions as its whole JsonLogic subset, with JsonLogicConditions
- phil/privacy-bundle: For access and erasure requests that find the entries about a person
- symfony/console: For phil:forms:purge
- symfony/http-client: To post entries to a webhook
- symfony/key-management: To seal the values of sensitive fields by envelope encryption under a master key of a KMS
- symfony/mailer: To email entries
- symfony/routing: To import config/routes.php, the page of each form
- symfony/security-core: To keep who sent an entry when they are signed in
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-15 03:41:30 UTC
README
Contact and registration forms defined in the database, with their entries kept only as long as needed.
phil/forms-bundle gives a Symfony application forms that live in its database. A form is a key, a title and fields:
text, paragraphs, email, URL, date, birth date, a choice, a checkbox. A field may be asked under a condition on the
fields before it, or only for some tiers — a kind of ticket, a membership —; a choice may disqualify the entry with
a message, or hold a number of places. The bundle gives each form a page at /forms/{key},
protected against spam by phil/spam-protection-bundle without a captcha or a third party. It keeps every entry no
longer than the form says, then phil:forms:purge deletes it. It tells whoever should hear of an entry by email from
the site's own address — to its recipients, and to other addresses under conditions —, by a webhook signed with
Standard Webhooks, or through the application's own notifiers. The values of sensitive fields never leave by email
or webhook, and are sealed under a master key of a KMS through Symfony's KeyManagement component; a form with a
sensitive field is refused while no sealer is configured. A birth date may take only some ages, counted on the day
the entry comes or on a date of the form's choosing. Its conditions read ==, != and in on one field; with
phil/form-logic, they also apply in the browser as the form is filled, and may read that library's whole subset of
JsonLogic, checked against the types of the fields.
It does not keep the visitor's IP address, browser or cookies: an entry holds the values, its language and its dates.
It never sends a copy to the visitor, which would make a form a relay for spam, and it never shows a captcha. To
answer an access or erasure request, an entry may also keep the signed-in person's identifier, or a keyed digest of an
email address the form designates. With phil/privacy-bundle, those entries are exported or erased by person.
It is for a site that collects registrations, questions or feedback, and whose forms are data the application creates
and changes at run time. Retention by form, from 1 to 3650 days, serves the purpose limits of Québec's Law 25 and the
GDPR. Symfony Mailer, the HTTP client, Console, Routing and Security are optional, each for the part that needs it.
Form::accept() is the one place values are checked, and every notifier implements EntryNotifier: the entry is
saved first, so a notifier that fails loses nothing.
Status
| Part | State |
|---|---|
Form, Entry, Field, FieldType |
Implemented and tested |
Conditions (ConditionEvaluator, BasicConditions), tiers, disqualifying choices, the birth date and its ages |
Implemented and tested |
Richer conditions through phil/form-logic's adapter JsonLogicConditions |
Implemented and tested, in that library |
Places in choices (ChoicePlaces), other notifications under conditions (Notification) |
Implemented and tested |
Sensitive fields sealed through Symfony's KeyManagement component (FieldSealer, KeyManagementFieldSealer, SealedValues), refused without a sealer (UnsealedSensitiveField, allow_unsealed_sensitive) |
Implemented and tested |
Conditions applied in the browser by phil/form-logic's Stimulus controller (LogicSteps), when that library is installed |
Implemented and tested |
Submissions, Retention, EntryNotifier |
Implemented and tested |
EntryType, FormController and config/routes.php |
Implemented and tested |
EmailNotifier, WebhookNotifier |
Implemented and tested |
DoctrineFormRepository, phil:forms:purge |
Implemented and tested |
| Flex recipe: the bundle, its configuration, its routes | Implemented and tested |
| Templates and translations, English and French | Implemented and tested |
Documentation
| Document | What it covers |
|---|---|
| Installation | Requirements, the recipe, registering, routes, tables, configuration. |
| Forms | Defining a form, its fields, what each accepts, conditions, tiers, places, sensitive fields. |
| Entries and retention | What is kept, for how long, and deleting it. |
| Notifications | Email, the webhook, and notifiers of the application's own. |
| Extension points | What an application implements, configures, replaces or relies on. |
| Testing | How the bundle is tested. |
| Reference | Every type of the bundle. |
Quick start
$form = Form::create('registration', 'Tournament registration', [ Field::of('name', 'Name', FieldType::Text, required: true), Field::of('email', 'Email', FieldType::Email, required: true), Field::of('team', 'Team', FieldType::Choice, choices: ['Blue', 'Green'], capacities: ['Blue' => 20, 'Green' => 20]), Field::of('captain', 'I will captain my team', FieldType::Checkbox, shownWhen: ['in' => [['var' => 'team'], ['Blue', 'Green']]]), Field::of('consent', 'I accept the rules', FieldType::Checkbox, required: true), ], retentionDays: 90); $form->notify(['games@example.com'], 'https://hooks.example.com/forms'); $form->notifyAlso([Notification::to(['blue@example.com'], 'Blue: {name}', ['==' => [['var' => 'team'], 'Blue']])]); $repository->save($form); // The page: /forms/registration
Requirements
PHP 8.5 or later with ext-filter and ext-mbstring; Doctrine ORM 3.7; phil/spam-protection-bundle 0.1 and
phil/standard-webhooks 0.1; Symfony 8.2's form, HTTP foundation and kernel, UID and dependency injection;
PSR-20's clock; Twig 3. Symfony Mailer, HTTP client, Console and Routing are optional, as are
phil/privacy-bundle and Symfony Security's core; symfony/key-management seals the values of sensitive fields,
and a form with one is refused without a sealer; phil/form-logic applies the conditions in the browser and reads
richer ones.
Contributing
Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.
License
MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.