Contact Form

GitHub GitHub last commit CI Workflow GitHub code size in bytes GitHub Issues Total Downloads Monthly Downloads

Requirements

The package service configuration imports the shared Form and Data Processor configurations, together with Twig Components’ optional Derafu Twig integration.

An HTTP client that implements PSR-18 and the PSR-17 factories for requests and streams, registered in the container as Psr\Http\Client\ClientInterface, Psr\Http\Message\RequestFactoryInterface and Psr\Http\Message\StreamFactoryInterface. The package sends the message to the webhook with them and does not depend on a particular client. The timeout of the request is the one of the client.

A CSRF token manager and the session that it uses (see CSRF Protection): the form is protected with a token, which its page renders and which is checked when the form is sent.

A captcha (see Captcha and derafu/captcha): the form is protected with it (captcha_protection: true), so the page renders the widget and a message that arrives without having solved it is not sent. The application has to configure a captcha, for example CAPTCHA_PROVIDER=altcha with a CAPTCHA_SECRET_KEY (free, no account), or CAPTCHA_PROVIDER=none to say, on purpose, that it has none; without it the page of the form fails with a message that says what to configure. A form that arrives without a valid token is not sent to the webhook, and the page shows “The form is not valid or has expired. Reload the page and try again.”

derafu/foundation already has it all: it requires this package, derafu/csrf, derafu/captcha, derafu/session and Guzzle, imports their routes and services, and registers Guzzle (with a timeout of 10 seconds) as the client. The website skeleton requires derafu/foundation, so it has no controller of its own for the contact page.

Check the configuration in the foundation-services.yaml of derafu/foundation.

Quick Start

Install the package:

composer require derafu/contact-form

Import the routes to your routes.yaml:

imports:
    - { resource: '../vendor/derafu/contact-form/resources/config/contact-form-routes.yaml' }

Import the services to your services.yaml:

imports:
    - { resource: '../vendor/derafu/contact-form/resources/config/contact-form-services.yaml' }

The packaged contact templates load automatically through Derafu\ContactForm\Provider\ContactFormTemplatePathProvider, tagged with derafu_twig.template_path_provider. Form, the contact pages and Twig Components reuse the shared Renderer and Twig environment.

Supply the application’s layouts/default.html.twig through its template-path provider. To customize a contact page, contribute the corresponding contact/index.html.twig, contact/success.html.twig or contact/unavailable.html.twig with a higher provider priority. Other package templates remain available; the application does not need to copy template-path lists or redefine the renderer.

Inject Derafu\ContactForm\Contract\ContactServiceInterface when using the contact service directly. It is registered with ContactService, and the controller receives the same interface. HTTP clients/factories, the translator, CSRF session integration and the selected captcha provider belong to the consuming application’s configuration.

Configure the environment variables:

# The contact form is on by default. FORM_CONTACT_ENABLED=false turns it off.
FORM_CONTACT_ENABLED=true

# Source of the form for identifying it in the webhook.
# Useful for multiple forms in the same webhook.
FORM_CONTACT_SOURCE=derafu.dev

# URL and secret key of the webhook that receives the messages. Both are required
# while the form is on.
# Get the URL from the webhook service you are using. The secret key is any long
# random text (`openssl rand -hex 32`): every message is signed with it, using HMAC.
FORM_CONTACT_WEBHOOK_URL=
FORM_CONTACT_WEBHOOK_SECRET_KEY=

Configuration parameters

The service configuration maps the existing environment variables to these package parameters:

Environment variable Parameter
FORM_CONTACT_ENABLED derafu.contact_form.enabled
FORM_CONTACT_SOURCE derafu.contact_form.source
FORM_CONTACT_WEBHOOK_URL derafu.contact_form.webhook.url
FORM_CONTACT_WEBHOOK_SECRET_KEY derafu.contact_form.webhook.secret_key

FORM_CONTACT_ENABLED defaults to true. The webhook URL and secret key are required while the form is enabled. The source can be supplied explicitly or inferred from the application’s kernel.context host configuration.

When the form is not available

The form needs the webhook to send its messages, so the package checks, before it shows the form, that it can send them:

Situation The pages (/contact, /contact/submit and /contact/success)
FORM_CONTACT_ENABLED=false A page that says that the contact form is not available, with a 200: it is not an error, it is a site that does not have the form.
Enabled, and FORM_CONTACT_WEBHOOK_URL or FORM_CONTACT_WEBHOOK_SECRET_KEY is not set The same page, with a 503: it is a configuration that is missing. A submission is not processed.

The visitor is only told that the form is not available. With kernel.debug on, the page also says what is wrong: that it is turned off, or the names of the variables that are missing.

The page is contact/unavailable.html.twig of the package, a page of its own and not the template of the form, so every site shows it whatever its copy of contact/index.html.twig has. It extends layouts/default.html.twig of the site, so it has its header and its footer. A site that wants another page puts its own templates/contact/unavailable.html.twig, the same way it replaces the form.

The secret key is required because every message is signed: the receiver decides whether to check the signature, and one that does not know how to ignores it, but the site always sends it.

When the message can not be sent (the webhook does not answer, or answers with an error), the page of the form shows the reason in an alert, above the form, which keeps what the user wrote so it can try again. The alert is in the template of the package: a site that copies contact/index.html.twig has to keep the block {% if error %}…{% endif %}, or the user is not told.

The token and the captcha of the form

The form definition has name: contact in its schema, which is the id that its token is asked for, so the token of another form of the site is not good for this one, and the captcha that was solved for another form is not good either. A site that uses its own form definition gives it its own name, and its own captcha_protection (the form of this package has it on; the template has {{ form_captcha(form) }} before the button).

Translations

The texts of the form (resources/forms/contact-form.yaml) are in English and the file says in which domain they are with the option translation_domain: contact-form. The form factory of derafu/form translates them when the form is created, in the language of the translator, with the Spanish catalogue of the package. A site that uses its own form definition gives it its own translation_domain and its own catalogue.

Tests

The tests of the package send the form for real: ContactService posts to a webhook that the test starts as a local PHP server (tests/fixtures/webhook-router.php), and the test reads what the server received, including the HMAC signature of the payload. The submission is tested with the real token manager of derafu/csrf and the ALTCHA provider of derafu/captcha (with a cheap challenge, which is solved as a browser does): a submission without the token or the captcha, or with the ones of another form, is not valid and is not sent. The controller is tested with the service and the renderer as doubles: the page that says that the form is not available (200 when it is turned off, 503 when it lacks the webhook or its secret key, and without processing the submission), and the form that is given back with what the user wrote when the message can not be sent. The messages of the package are audited with TwigTranslationAudit, and the texts of the form with FormTranslationAudit, so a text without translation fails the test.

On this page

#php
Last updated on 30/10/2026 by Anonymous