From 2369a30abbde5d6cd4bd0004a3da10ae591082e8 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 22 Jan 2026 13:10:33 +0100 Subject: [PATCH] [Form] Explain more basic concepts about Symfony forms --- forms.rst | 345 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 311 insertions(+), 34 deletions(-) diff --git a/forms.rst b/forms.rst index 4aed1ab51e..c271f519aa 100644 --- a/forms.rst +++ b/forms.rst @@ -21,6 +21,78 @@ install the form feature before using it: $ composer require symfony/form +Understanding How Forms Work +---------------------------- + +Before diving into the code, it's helpful to understand the mental model behind +Symfony forms. Think of a form as a **bidirectional mapping layer** between +your PHP objects (or arrays) and HTML forms. + +This mapping works in two directions: + +#. **Object to HTML**: When rendering a form, Symfony reads data from your + object and turns it into HTML fields that users can edit; + +#. **HTML to Object**: When processing a submission, Symfony takes the raw + values from the HTTP request (typically strings) and converts them back into + the appropriate PHP types on your object. + +This flow is the core of the Form component. From simple text fields to complex +nested collections, everything follows the same pattern. + +.. _form-data-lifecycle: + +The Data Transformation Lifecycle +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Data in a form goes through three representations, often called **data layers**: + +**Model Data** + The data in the format your application uses. For example, a ``DateTime`` + object, a Doctrine entity, or a custom value object. This is what you pass + to ``createForm()`` and what you get back after a successful submission via + ``getData()``. + +**Normalized Data** + An intermediate representation that normalizes the model data. For most + field types, this is identical to the model data. However, for some types + it's different. For example, ``DateType`` is normalized as an array with + ``year``, ``month``, and ``day`` keys. + +**View Data** + The format used to populate HTML form fields and received from user + submissions. In most cases, this is string-based (or arrays of strings), + because browsers submit text. Some fields may use other representations or + remain empty for security reasons (for example, file inputs). + +High-level flow: + +**Form Rendering** + +#. Start with model data from your object. +#. Model transformers convert it to normalized data. +#. View transformers convert it to view data (typically strings). +#. Symfony renders the corresponding HTML widgets. + +**Form Submission** + +#. Symfony reads raw values from the HTTP request (typically strings). +#. View transformers reverse the data into normalized data. +#. Model transformers reverse the data into model data. +#. The data is written back to the underlying object or array. + +For a ``DateType`` field configured to render as three ````). +Changing the Form Field Names and Ids +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you want to modify this, use the :method:`Symfony\\Component\\Form\\FormFactoryInterface::createNamed` -method:: +When Symfony renders a form, it generates HTML ``name`` and ``id`` attributes +for each field following specific conventions. Understanding these conventions +helps when writing JavaScript, CSS selectors, or custom form themes. - // src/Controller/TaskController.php - namespace App\Controller; +In Twig templates, prefer ``form.vars.full_name`` and ``form.vars.id`` as the +source of truth, instead of reconstructing names manually. - use App\Form\TaskType; - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Form\FormFactoryInterface; - // ... +**The ``name`` Attribute** - class TaskController extends AbstractController - { - public function new(FormFactoryInterface $formFactory): Response - { - $task = ...; - $form = $formFactory->createNamed('my_name', TaskType::class, $task); +Field names follow the pattern: ``formName[fieldName]``. For nested forms, names +are further nested: ``formName[childForm][fieldName]``. - // ... - } - } +Given a ``TaskType`` form with a ``dueDate`` field:: -You can even suppress the name completely by setting it to an empty string. + $form = $this->createForm(TaskType::class, $task); + +The rendered HTML will have: + +.. code-block:: html + + + +For a ``DateType`` field that renders as three separate ``... + + + +**The ``id`` Attribute** + +The ``id`` attribute follows a similar pattern but uses underscores instead of +brackets: ``formName_fieldName``. For the examples above: + +.. code-block:: html + + + + + + + + +**Customizing the Form Name** + +The default form name is derived from the form type class (for example, +``TaskType`` becomes ``task`` and ``FooBarType`` becomes ``foo_bar``). You can +customize this by returning a different value from the ``getBlockPrefix()`` method +of your form type class. + +You can also customize this by creating the form with the +:method:`Symfony\\Component\\Form\\FormFactoryInterface::createNamed` method:: + + // using FormFactory + $form = $formFactory->createNamed('my_task', TaskType::class, $task); + + // this generates: + +To create a form without any name prefix (fields named directly like ``dueDate`` +instead of ``task[dueDate]``):: + + $form = $formFactory->createNamed('', TaskType::class, $task); .. _forms-html5-validation-disable: @@ -1098,6 +1313,8 @@ If you'd like to change one of the guessed values, override it in the options fi if you're using a Doctrine entity. Read :ref:`automatic_object_validation` guide for more information. +.. _form-unmapped-fields: + Unmapped Fields ~~~~~~~~~~~~~~~ @@ -1135,6 +1352,8 @@ These "unmapped fields" can be set and accessed in a controller with:: Additionally, if there are any fields on the form that aren't included in the submitted data, those fields will be explicitly set to ``null``. +.. _form-extra-fields: + Extra fields ~~~~~~~~~~~~ @@ -1396,7 +1615,7 @@ To achieve this, use the ``expression`` option of the ] ]) - // this field is only required if the value of the 'how_did_you_hear' field is 'other' + // this field is only required when 'how_did_you_hear' is 'other' ->add('other_text', TextType::class, [ 'required' => false, 'label' => 'Please specify', @@ -1411,6 +1630,64 @@ To achieve this, use the ``expression`` option of the ]) ; +Troubleshooting +--------------- + +Why Doesn't My Field Value Display? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Problem**: The form renders, but a field is empty even though the underlying +data has a value. + +**Common causes**: + +#. The property is not readable (missing accessor, wrong name, or not public). + For booleans, Symfony also looks for ``is*()`` and ``has*()`` accessors. + +#. The field name doesn't match the property name. Use :ref:`property_path ` + if you need to map to a different property. + +#. The data is set after creating the form. Populate your object *before* + passing it to ``createForm()``:: + + // wrong: object populated after form creation + $form = $this->createForm(TaskType::class, $task); + $task->setTask('My task'); + + // correct: object populated before form creation + $task->setTask('My task'); + $form = $this->createForm(TaskType::class, $task); + +Why Doesn't My Submitted Data Save to the Object? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Problem**: The form submits, but the object properties remain unchanged. + +**Common causes**: + +#. The property is not writable (missing setter, wrong name, or not public). + +#. It is an :ref:`unmapped field `. + +#. The form is not synchronized due to a transformation failure. Check + ``isSynchronized()`` and inspect field errors. + +Why Does ``getData()`` Return ``null`` After Submission? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Problem**: ``$form->getData()`` is ``null`` after handling the request. + +**Common causes**: + +#. No initial object (or default data) was provided and the form doesn't create + one. Review the form's ``data_class`` and ``empty_data`` options. + +#. A transformation failed and the form is not synchronized. Check + ``isSynchronized()`` and field errors. + +#. The form is not submitted or is invalid. Check ``isSubmitted()`` and + ``isValid()`` before using the data. + Learn more ----------