Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Symfony Form
https://github.com/symfony/form
Admin
The Symfony Form component simplifies the creation, processing, and reuse of HTML forms.
Tokens:
15,858
Snippets:
57
Trust Score:
9.2
Update:
1 week ago
Context
Skills
Chat
Benchmark
78.8
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Symfony Form Component The Symfony Form component is a powerful PHP library for creating, processing, and validating HTML forms in web applications. It provides a complete form handling solution with support for complex form structures, data transformation between different representations (model, normalized, and view), validation integration with the Symfony Validator component, and flexible rendering through a view system. The component follows an object-oriented architecture with form types, builders, and factories that enable both simple forms and sophisticated multi-step wizards. At its core, the component uses a three-layer data transformation pipeline: model data (your domain objects), normalized data (internal processing format), and view data (HTML-ready strings/arrays). This architecture allows seamless handling of various data types while maintaining a clean separation between your business logic and presentation layer. The component includes 30+ built-in form types, an event system for dynamic form modification, CSRF protection, and extensibility through custom types and extensions. ## Forms::createFormFactory Creates a form factory with default configuration. This is the main entry point for using the Form component standalone. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; // Create the form factory $formFactory = Forms::createFormFactory(); // Build a simple form $form = $formFactory->createBuilder() ->add('firstName', TextType::class) ->add('lastName', TextType::class) ->add('age', IntegerType::class) ->add('color', ChoiceType::class, [ 'choices' => ['Red' => 'r', 'Blue' => 'b', 'Green' => 'g'], ]) ->getForm(); // Submit data to the form $form->submit([ 'firstName' => 'John', 'lastName' => 'Doe', 'age' => 30, 'color' => 'b', ]); // Check validity and retrieve data if ($form->isSubmitted() && $form->isValid()) { $data = $form->getData(); // ['firstName' => 'John', 'lastName' => 'Doe', 'age' => 30, 'color' => 'b'] } ``` ## Forms::createFormFactoryBuilder Creates a form factory builder for custom configuration with extensions, custom types, and validators. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Validator\ValidatorExtension; use Symfony\Component\Validator\Validation; // Create validator $validator = Validation::createValidator(); // Create form factory with validator extension $formFactory = Forms::createFormFactoryBuilder() ->addExtension(new ValidatorExtension($validator)) ->getFormFactory(); // You can also add custom types and type extensions $formFactory = Forms::createFormFactoryBuilder() ->addType(new CustomType()) ->addTypeExtension(new CustomTypeExtension()) ->getFormFactory(); ``` ## FormFactoryInterface::create Creates a form directly from a type class. Use this when you don't need to customize the form builder. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; $formFactory = Forms::createFormFactory(); // Create a simple text field $textForm = $formFactory->create(TextType::class, 'default value', [ 'required' => true, 'attr' => ['maxlength' => 100], ]); // Create a choice field with options $choiceForm = $formFactory->create(ChoiceType::class, null, [ 'choices' => [ 'Yes' => true, 'No' => false, 'N/A' => null, ], 'placeholder' => 'Choose an option', ]); $choiceForm->submit('1'); // Submit 'true' $data = $choiceForm->getData(); // true (boolean) ``` ## FormFactoryInterface::createNamed Creates a named form, useful when you need to control the form's name attribute in HTML. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\EmailType; $formFactory = Forms::createFormFactory(); // Create a named form - the name affects HTML field names $form = $formFactory->createNamed('contact', FormType::class) ->add('name', TextType::class) ->add('email', EmailType::class); // HTML fields will be named: contact[name], contact[email] // Submit with the correct structure $form->submit([ 'name' => 'Jane Doe', 'email' => 'jane@example.com', ]); echo $form->isValid(); // true ``` ## FormFactoryInterface::createBuilder Creates a form builder for fluent form construction with full control over fields, transformers, and events. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; $formFactory = Forms::createFormFactory(); // Create a builder and add fields fluently $builder = $formFactory->createBuilder(FormType::class, null, [ 'method' => 'POST', 'action' => '/login', ]); $builder ->add('username', TextType::class, [ 'required' => true, 'label' => 'Username', 'attr' => ['placeholder' => 'Enter username'], ]) ->add('password', PasswordType::class, [ 'required' => true, 'label' => 'Password', ]) ->add('submit', SubmitType::class, [ 'label' => 'Login', ]); // Build the form $form = $builder->getForm(); // Access child builders before building $usernameBuilder = $builder->get('username'); $builder->remove('submit'); // Remove a field $builder->has('username'); // true ``` ## FormBuilderInterface::add Adds a child field to a form builder. Accepts field name, type, and options. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\MoneyType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; $formFactory = Forms::createFormFactory(); $builder = $formFactory->createBuilder(FormType::class); // Add various field types with options $builder ->add('title', TextType::class, [ 'required' => true, 'trim' => true, 'attr' => ['class' => 'form-control'], ]) ->add('publishDate', DateType::class, [ 'widget' => 'single_text', 'input' => 'datetime', 'format' => 'yyyy-MM-dd', ]) ->add('price', MoneyType::class, [ 'currency' => 'USD', 'scale' => 2, ]) ->add('category', ChoiceType::class, [ 'choices' => [ 'Technology' => 'tech', 'Science' => 'science', 'Arts' => 'arts', ], 'expanded' => false, // dropdown 'multiple' => false, // single selection ]); $form = $builder->getForm(); ``` ## FormInterface::submit Submits data to the form, triggering validation and data transformation. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\TextType; $formFactory = Forms::createFormFactory(); $form = $formFactory->createBuilder(FormType::class) ->add('firstName', TextType::class) ->add('lastName', TextType::class) ->getForm(); // Full submission - clears missing fields $form->submit([ 'firstName' => 'John', 'lastName' => 'Doe', ]); // Partial submission - preserves missing fields (PATCH-like behavior) $form2 = $formFactory->createBuilder(FormType::class) ->add('firstName', TextType::class) ->add('lastName', TextType::class) ->getForm(); $form2->setData(['firstName' => 'Original', 'lastName' => 'Name']); $form2->submit(['firstName' => 'Updated'], false); // clearMissing = false echo $form2->getData()['firstName']; // 'Updated' echo $form2->getData()['lastName']; // 'Name' (preserved) // Check submission state echo $form->isSubmitted(); // true echo $form->isValid(); // true (if no validation errors) ``` ## FormInterface::getData / getNormData / getViewData Retrieves form data in different representations along the transformation pipeline. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\MoneyType; $formFactory = Forms::createFormFactory(); // Date example showing data transformation $dateForm = $formFactory->create(DateType::class, null, [ 'widget' => 'single_text', 'input' => 'datetime', ]); $dateForm->submit('2024-01-15'); // getData() - Model data (what your application works with) $modelData = $dateForm->getData(); // DateTime object: 2024-01-15 // getNormData() - Normalized data (internal processing format) $normData = $dateForm->getNormData(); // DateTime object (same as model for dates) // getViewData() - View data (what appears in HTML) $viewData = $dateForm->getViewData(); // String: '2024-01-15' // Money example $moneyForm = $formFactory->create(MoneyType::class, null, [ 'currency' => 'EUR', ]); $moneyForm->submit('1234.56'); echo $moneyForm->getData(); // float: 1234.56 (model) echo $moneyForm->getViewData(); // string: '1234.56' (view) ``` ## FormInterface::getErrors Retrieves validation errors from a form, with options for deep traversal and flattening. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\FormError; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\EmailType; $formFactory = Forms::createFormFactory(); $form = $formFactory->createBuilder(FormType::class) ->add('name', TextType::class) ->add('email', EmailType::class) ->getForm(); $form->submit(['name' => '', 'email' => 'invalid']); // Add errors manually (normally added by validator) $form->get('name')->addError(new FormError('Name is required')); $form->get('email')->addError(new FormError('Invalid email format')); $form->addError(new FormError('Form-level error')); // Get errors for current form only (not children) $formErrors = $form->getErrors(); foreach ($formErrors as $error) { echo $error->getMessage(); // 'Form-level error' } // Get all errors including children (deep) $allErrors = $form->getErrors(true); foreach ($allErrors as $error) { echo $error->getMessage(); // 'Form-level error', 'Name is required', 'Invalid email format' } // Get all errors flattened (deep + flatten) $flatErrors = $form->getErrors(true, true); foreach ($flatErrors as $error) { echo $error->getOrigin()->getName() . ': ' . $error->getMessage(); } ``` ## FormInterface::createView Creates a FormView object for rendering the form in templates. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; $formFactory = Forms::createFormFactory(); $form = $formFactory->createBuilder(FormType::class) ->add('name', TextType::class, ['label' => 'Full Name']) ->add('status', ChoiceType::class, [ 'choices' => ['Active' => 'active', 'Inactive' => 'inactive'], ]) ->getForm(); $form->submit(['name' => 'John Doe', 'status' => 'active']); // Create the view for rendering $view = $form->createView(); // Access view variables echo $view->vars['id']; // Form ID for HTML echo $view->vars['name']; // Form name echo $view->vars['full_name'];// Full HTML name attribute echo $view->vars['value']; // Current value echo $view->vars['method']; // HTTP method (POST) echo $view->vars['action']; // Form action URL echo $view->vars['valid']; // Validation status // Access child views $nameView = $view['name']; echo $nameView->vars['label']; // 'Full Name' echo $nameView->vars['value']; // 'John Doe' echo $nameView->vars['required']; // true echo $nameView->vars['id']; // HTML id attribute // Iterate children foreach ($view as $childView) { echo $childView->vars['name']; } ``` ## ChoiceType Renders select dropdowns, radio buttons, or checkboxes depending on options. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; $formFactory = Forms::createFormFactory(); // Simple dropdown $dropdown = $formFactory->create(ChoiceType::class, null, [ 'choices' => [ 'Small' => 's', 'Medium' => 'm', 'Large' => 'l', ], 'placeholder' => 'Select size', ]); // Radio buttons (expanded + single) $radios = $formFactory->create(ChoiceType::class, null, [ 'choices' => [ 'Option A' => 'a', 'Option B' => 'b', 'Option C' => 'c', ], 'expanded' => true, 'multiple' => false, ]); // Checkboxes (expanded + multiple) $checkboxes = $formFactory->create(ChoiceType::class, null, [ 'choices' => [ 'Red' => 'red', 'Green' => 'green', 'Blue' => 'blue', ], 'expanded' => true, 'multiple' => true, ]); // Grouped choices $grouped = $formFactory->create(ChoiceType::class, null, [ 'choices' => [ 'Fruits' => [ 'Apple' => 'apple', 'Banana' => 'banana', ], 'Vegetables' => [ 'Carrot' => 'carrot', 'Lettuce' => 'lettuce', ], ], ]); // Multi-select dropdown $multiSelect = $formFactory->create(ChoiceType::class, null, [ 'choices' => ['A' => 'a', 'B' => 'b', 'C' => 'c'], 'multiple' => true, 'expanded' => false, ]); $multiSelect->submit(['a', 'c']); $data = $multiSelect->getData(); // ['a', 'c'] // With preferred choices (shown first) $preferred = $formFactory->create(ChoiceType::class, null, [ 'choices' => [ 'United States' => 'us', 'Canada' => 'ca', 'France' => 'fr', 'Germany' => 'de', 'Japan' => 'jp', ], 'preferred_choices' => ['us', 'ca'], ]); ``` ## CollectionType Manages collections of repeated form elements with add/remove capabilities. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\EmailType; $formFactory = Forms::createFormFactory(); // Simple collection of text fields (emails) $form = $formFactory->createBuilder(FormType::class) ->add('emails', CollectionType::class, [ 'entry_type' => EmailType::class, 'entry_options' => ['label' => false], 'allow_add' => true, 'allow_delete' => true, 'prototype' => true, 'prototype_name' => '__email__', ]) ->getForm(); // Set initial data $form->setData([ 'emails' => ['john@example.com', 'jane@example.com'], ]); // Submit with modified collection $form->submit([ 'emails' => [ 'john@example.com', // kept 'new@example.com', // added // jane@example.com removed ], ]); $data = $form->getData(); // ['emails' => ['john@example.com', 'new@example.com']] // Collection with compound entry type $builder = $formFactory->createBuilder(FormType::class) ->add('contacts', CollectionType::class, [ 'entry_type' => FormType::class, 'entry_options' => [ 'data_class' => null, ], 'allow_add' => true, 'prototype' => true, 'delete_empty' => true, // Auto-delete empty entries ]); // Add fields to collection entry via event listener $builder->get('contacts')->addEventListener( \Symfony\Component\Form\FormEvents::POST_SET_DATA, function ($event) { $form = $event->getForm(); foreach ($form as $entry) { $entry->add('name', TextType::class); $entry->add('email', EmailType::class); } } ); ``` ## DateType and DateTimeType Date and datetime pickers with various widget and input format options. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; use Symfony\Component\Form\Extension\Core\Type\TimeType; $formFactory = Forms::createFormFactory(); // HTML5 date input (single_text widget) $html5Date = $formFactory->create(DateType::class, null, [ 'widget' => 'single_text', 'html5' => true, 'input' => 'datetime', // Returns DateTime object ]); $html5Date->submit('2024-06-15'); $date = $html5Date->getData(); // DateTime object // Date with select dropdowns $selectDate = $formFactory->create(DateType::class, null, [ 'widget' => 'choice', 'years' => range(date('Y') - 100, date('Y')), 'format' => 'dd-MM-yyyy', ]); // DateTime with various options $dateTime = $formFactory->create(DateTimeType::class, null, [ 'widget' => 'single_text', 'input' => 'datetime_immutable', // Returns DateTimeImmutable 'with_seconds' => false, 'model_timezone' => 'UTC', 'view_timezone' => 'America/New_York', ]); // Time only $time = $formFactory->create(TimeType::class, null, [ 'widget' => 'single_text', 'input' => 'string', 'input_format' => 'H:i', 'with_seconds' => false, ]); $time->submit('14:30'); echo $time->getData(); // '14:30' // Date with string input $stringDate = $formFactory->create(DateType::class, null, [ 'widget' => 'single_text', 'input' => 'string', 'input_format' => 'Y-m-d', ]); $stringDate->submit('2024-06-15'); echo $stringDate->getData(); // '2024-06-15' (string) ``` ## DataTransformerInterface and CallbackTransformer Transform data between model, normalized, and view representations. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; $formFactory = Forms::createFormFactory(); // Using CallbackTransformer for simple transformations $builder = $formFactory->createBuilder(TextType::class); $builder->addViewTransformer(new CallbackTransformer( // transform: model -> view (for display) function ($value) { return $value ? strtoupper($value) : ''; }, // reverseTransform: view -> model (on submit) function ($value) { return $value ? strtolower($value) : ''; } )); $form = $builder->getForm(); $form->setData('hello'); $view = $form->createView(); echo $view->vars['value']; // 'HELLO' $form->submit('WORLD'); echo $form->getData(); // 'world' // Custom DataTransformer class class JsonToArrayTransformer implements DataTransformerInterface { public function transform(mixed $value): mixed { if (null === $value) { return ''; } return json_encode($value, JSON_PRETTY_PRINT); } public function reverseTransform(mixed $value): mixed { if (empty($value)) { return []; } $decoded = json_decode($value, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new TransformationFailedException('Invalid JSON'); } return $decoded; } } $jsonBuilder = $formFactory->createBuilder(TextType::class); $jsonBuilder->addViewTransformer(new JsonToArrayTransformer()); $jsonForm = $jsonBuilder->getForm(); $jsonForm->setData(['name' => 'John', 'age' => 30]); // View shows: {"name": "John", "age": 30} $jsonForm->submit('{"city": "NYC"}'); $data = $jsonForm->getData(); // ['city' => 'NYC'] ``` ## FormEvents and Event Listeners Modify forms dynamically using the event system during various lifecycle stages. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; $formFactory = Forms::createFormFactory(); $builder = $formFactory->createBuilder(FormType::class); $builder->add('country', ChoiceType::class, [ 'choices' => [ 'United States' => 'us', 'Canada' => 'ca', 'Other' => 'other', ], ]); // PRE_SET_DATA: Modify form based on initial data $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { $data = $event->getData(); $form = $event->getForm(); // Add fields based on existing data if ($data && isset($data['country']) && $data['country'] === 'us') { $form->add('state', ChoiceType::class, [ 'choices' => [ 'California' => 'CA', 'New York' => 'NY', 'Texas' => 'TX', ], ]); } }); // PRE_SUBMIT: Modify submitted data or form structure $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) { $data = $event->getData(); $form = $event->getForm(); // Normalize email to lowercase if (isset($data['email'])) { $data['email'] = strtolower($data['email']); $event->setData($data); } // Dynamically add state field if US selected if (isset($data['country']) && $data['country'] === 'us') { $form->add('state', ChoiceType::class, [ 'choices' => ['CA' => 'CA', 'NY' => 'NY', 'TX' => 'TX'], ]); } }); // POST_SUBMIT: Access final data after transformation $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { $form = $event->getForm(); if ($form->isValid()) { $data = $form->getData(); // Process valid data } }); $builder->add('email', TextType::class); $form = $builder->getForm(); $form->submit([ 'country' => 'us', 'email' => 'JOHN@EXAMPLE.COM', 'state' => 'CA', ]); echo $form->getData()['email']; // 'john@example.com' (normalized) ``` ## AbstractType - Custom Form Types Create reusable custom form types by extending AbstractType. ```php <?php use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\TelType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\Forms; class AddressType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('street', TextType::class, [ 'required' => true, ]) ->add('city', TextType::class, [ 'required' => true, ]) ->add('postalCode', TextType::class, [ 'required' => true, ]) ->add('country', ChoiceType::class, [ 'choices' => $options['countries'], ]); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'countries' => [ 'United States' => 'US', 'Canada' => 'CA', ], ]); } } class PersonType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('firstName', TextType::class) ->add('lastName', TextType::class) ->add('email', EmailType::class) ->add('phone', TelType::class, ['required' => false]) ->add('address', AddressType::class, [ 'countries' => $options['available_countries'], ]); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => null, // Or your Person entity class 'available_countries' => [ 'US' => 'US', 'CA' => 'CA', 'UK' => 'UK', ], ]); } } // Register and use custom types $formFactory = Forms::createFormFactoryBuilder() ->addType(new AddressType()) ->addType(new PersonType()) ->getFormFactory(); $form = $formFactory->create(PersonType::class, null, [ 'available_countries' => ['USA' => 'US', 'Mexico' => 'MX'], ]); $form->submit([ 'firstName' => 'John', 'lastName' => 'Doe', 'email' => 'john@example.com', 'phone' => '+1-555-0123', 'address' => [ 'street' => '123 Main St', 'city' => 'Anytown', 'postalCode' => '12345', 'country' => 'US', ], ]); $data = $form->getData(); // Nested array with all form data ``` ## Data Binding with Objects Bind forms to entity/model objects with automatic property mapping. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\IntegerType; // Simple entity class class User { private string $firstName = ''; private string $lastName = ''; private string $email = ''; private int $age = 0; public function getFirstName(): string { return $this->firstName; } public function setFirstName(string $value): void { $this->firstName = $value; } public function getLastName(): string { return $this->lastName; } public function setLastName(string $value): void { $this->lastName = $value; } public function getEmail(): string { return $this->email; } public function setEmail(string $value): void { $this->email = $value; } public function getAge(): int { return $this->age; } public function setAge(int $value): void { $this->age = $value; } } $formFactory = Forms::createFormFactory(); // Create form bound to User class $form = $formFactory->createBuilder(FormType::class, null, [ 'data_class' => User::class, ]) ->add('firstName', TextType::class) ->add('lastName', TextType::class) ->add('email', EmailType::class) ->add('age', IntegerType::class) ->getForm(); // Submit data - creates User object automatically $form->submit([ 'firstName' => 'Alice', 'lastName' => 'Smith', 'email' => 'alice@example.com', 'age' => 28, ]); $user = $form->getData(); // User object with all properties set echo $user->getFirstName(); // 'Alice' echo $user->getEmail(); // 'alice@example.com' // Edit existing object $existingUser = new User(); $existingUser->setFirstName('Bob'); $existingUser->setEmail('bob@example.com'); $editForm = $formFactory->createBuilder(FormType::class, $existingUser, [ 'data_class' => User::class, ]) ->add('firstName', TextType::class) ->add('email', EmailType::class) ->getForm(); // Form is pre-populated with existing data $view = $editForm->createView(); echo $view['firstName']->vars['value']; // 'Bob' // Submit updates the existing object $editForm->submit(['firstName' => 'Robert', 'email' => 'robert@example.com']); echo $existingUser->getFirstName(); // 'Robert' (same object, updated) ``` ## FormTypeExtensionInterface Extend existing form types to add functionality across all forms. ```php <?php use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\Forms; class HelpTextExtension extends AbstractTypeExtension { public static function getExtendedTypes(): iterable { // Extend all form types (FormType is the parent of all) return [FormType::class]; } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'help_text' => null, 'help_html' => false, ]); } public function buildView(FormView $view, FormInterface $form, array $options): void { $view->vars['help_text'] = $options['help_text']; $view->vars['help_html'] = $options['help_html']; } } class PlaceholderExtension extends AbstractTypeExtension { public static function getExtendedTypes(): iterable { return [FormType::class]; } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'auto_placeholder' => false, ]); } public function buildForm(FormBuilderInterface $builder, array $options): void { if ($options['auto_placeholder']) { $currentAttr = $builder->getOption('attr') ?? []; if (!isset($currentAttr['placeholder'])) { $currentAttr['placeholder'] = $builder->getOption('label') ?? ucfirst($builder->getName()); $builder->setAttributes(['attr' => $currentAttr]); } } } } // Register extensions $formFactory = Forms::createFormFactoryBuilder() ->addTypeExtension(new HelpTextExtension()) ->getFormFactory(); // All forms now support help_text option $form = $formFactory->createBuilder() ->add('username', \Symfony\Component\Form\Extension\Core\Type\TextType::class, [ 'help_text' => 'Enter your username (3-20 characters)', 'help_html' => false, ]) ->getForm(); $view = $form->createView(); echo $view['username']->vars['help_text']; // 'Enter your username...' ``` ## Form Flows (Multi-step Forms) Create multi-step wizard forms using FormFlow (Symfony 7.4+). ```php <?php use Symfony\Component\Form\Flow\AbstractFormFlowType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; class RegistrationFlowType extends AbstractFormFlowType { protected function buildSteps(): array { return [ 'account' => [ 'label' => 'Account Information', ], 'profile' => [ 'label' => 'Profile Details', ], 'confirmation' => [ 'label' => 'Confirm & Submit', ], ]; } public function buildForm(FormBuilderInterface $builder, array $options): void { $step = $options['flow_step']; switch ($step) { case 'account': $builder ->add('email', EmailType::class) ->add('username', TextType::class); break; case 'profile': $builder ->add('firstName', TextType::class) ->add('lastName', TextType::class) ->add('bio', TextareaType::class, ['required' => false]); break; case 'confirmation': // Review step - read-only or confirmation checkbox $builder->add('acceptTerms', ChoiceType::class, [ 'choices' => ['I accept the terms' => true], 'expanded' => true, ]); break; } } } // Usage (conceptual - requires full Symfony integration) // $flow = $formFactory->create(RegistrationFlowType::class); // $flow->newStepForm(); // // if ($flow->isSubmitted() && $flow->isValid()) { // if ($flow->isFinished()) { // // All steps complete, save data // $data = $flow->getData(); // } else { // $flow->moveNext(); // } // } ``` ## Choice Loaders for Dynamic Choices Load choices dynamically from databases or external sources. ```php <?php use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader; use Symfony\Component\Form\ChoiceList\Loader\AbstractChoiceLoader; $formFactory = Forms::createFormFactory(); // Simple callback choice loader $form = $formFactory->create(ChoiceType::class, null, [ 'choice_loader' => new CallbackChoiceLoader(function () { // Simulate database query return [ 'admin' => 'Administrator', 'user' => 'Regular User', 'guest' => 'Guest', ]; }), 'choice_label' => function ($value) { return $value; // The label is the value from our array }, ]); // Custom choice loader with value extraction class CategoryChoiceLoader extends AbstractChoiceLoader { private array $categories; public function __construct() { // Simulate loading from database $this->categories = [ ['id' => 1, 'name' => 'Electronics'], ['id' => 2, 'name' => 'Clothing'], ['id' => 3, 'name' => 'Books'], ]; } protected function loadChoices(): array { return $this->categories; } } $categoryForm = $formFactory->create(ChoiceType::class, null, [ 'choice_loader' => new CategoryChoiceLoader(), 'choice_value' => function ($category) { return $category ? $category['id'] : ''; }, 'choice_label' => function ($category) { return $category['name']; }, ]); // Lazy choice loader (loads only when needed) $lazyForm = $formFactory->create(ChoiceType::class, null, [ 'choice_loader' => new CallbackChoiceLoader(function () { // Heavy operation - only called when rendering or validating sleep(0); // Simulated delay return ['opt1' => 'Option 1', 'opt2' => 'Option 2']; }), 'choice_lazy' => true, // Symfony 7.2+ ]); ``` The Symfony Form component excels at building complex, data-bound forms with robust validation and transformation pipelines. Common use cases include user registration forms, multi-step wizards, CRUD interfaces for entities, dynamic forms that change based on user input, and complex data entry systems with nested objects and collections. The component integrates seamlessly with Symfony's Validator component for constraint-based validation and with Twig for powerful form rendering with customizable themes. For integration, the recommended approach is to use the Forms::createFormFactory() entry point for standalone usage, or leverage Symfony's dependency injection container in full-stack applications. Custom form types should extend AbstractType and be registered as services. Data transformers handle the conversion between your domain objects and HTML-friendly representations, while event listeners enable dynamic form modification. The FormView system provides all necessary data for rendering in any templating engine, with first-class Twig support through form themes and helper functions.