symfony/form
Symfony Form component helps you build, process, validate, and reuse HTML forms with a robust, extensible API. Includes field types, data mapping, CSRF protection, and integration hooks for Symfony apps or standalone PHP projects.
Installation:
composer require symfony/form
Laravel already includes Symfony’s Form component, so no additional configuration is needed.
First Use Case:
Create a simple form for a User model in a controller:
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
->add('save', SubmitType::class);
}
Rendering in Blade:
{!! Form::open(['route' => 'users.store']) !!}
{!! $form->text('name') !!}
{!! $form->submit('Save') !!}
{!! Form::close() !!}
(Use collect($form->all()) to render fields dynamically.)
Handling Submission:
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user = $form->getData();
// Save to DB
}
Form::create() helper or inject FormFactoryInterface.TextType, EmailType, ChoiceType, CollectionType (for nested forms).namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
->add('password', PasswordType::class);
}
}
AbstractType for domain-specific forms (e.g., ProductType, OrderType).$form = $this->createForm(UserType::class, $existingUser);
Validator component (included in Laravel) with attributes:
use Symfony\Component\Validator\Constraints as Assert;
class User
{
#[Assert\Email]
public string $email;
}
$builder->add('tags', CollectionType::class, [
'entry_type' => TextType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
]);
$builder->add('role', EntityType::class, [
'class' => Role::class,
'choice_label' => 'name',
]);
FormFlow (v8+) with session storage:
use Symfony\Component\Form\FormFlow;
$flow = new FormFlow();
$flow->addStep('step1', new UserType());
$flow->addStep('step2', new AddressType());
$form = $flow->createForm();
$form->handleRequest($request);
if ($flow->isFinished()) {
$data = $flow->getData();
}
session.storage.handler in config/session.php to avoid serialization issues.{{ form_start(form) }}
{{ form_row(form.name) }}
{{ form_errors(form) }}
{{ form_end(form) }}
resources/views/vendor/form/fields.html.twig for global styling.Form::open() with routes:
Form::open(['route' => 'users.store']);
$builder->add('website', HiddenType::class, ['attr' => ['class' => 'honeypot']]);
$builder->add('avatar', FileType::class, [
'label' => 'Profile Picture',
'mapped' => false, // Handle manually
]);
#[Assert\File(mimeTypes: {'image/jpeg', 'image/png'})]
public ?UploadedFile $avatar;
use Symfony\Component\Form\Test\TypeTestCase;
class UserTypeTest extends TypeTestCase
{
public function testSubmitValidData()
{
$formData = ['name' => 'John'];
$form = $this->factory->create(UserType::class);
$form->submit($formData);
$this->assertTrue($form->isSubmitted());
$this->assertEquals($formData, $form->getData());
}
}
actingAs() and post() to test form submissions.Session Serialization Errors:
FormFlow.by_reference: true or implement __serialize()/__unserialize():
public function __serialize(): array { return ['data']; }
public function __unserialize(array $data): void { $this->data = $data['data']; }
Mismatched Indices in Collections:
tags[0][name] vs. tags[1][name]) causes data loss.allow_add: true and prototype: true:
$builder->add('tags', CollectionType::class, [
'prototype' => true,
'entry_options' => ['label' => false],
]);
CSRF Token Mismatch:
Form::createView():
$formView = $form->createView();
return response()->json(['csrf_token' => $formView->vars['csrf_token']]);
Validation Overrides:
pre_set_data or post_set_data callbacks:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$data = $event->getData();
if ($data && $data->isActive()) {
$event->getForm()->add('status', HiddenType::class, ['data' => 'active']);
}
});
DateTime Handling:
DateTimeType:
$builder->add('birthday', DateType::class, [
'widget' => 'single_text',
'html5' => true,
'attr' => ['class' => 'datepicker'],
]);
Performance with Large Forms:
FormTheme to reduce template complexity.FormEvents::PRE_SUBMIT.Dump Form Data:
dd($form->getData(), $form->getErrors(true), $form->getNormData());
Enable Debug Toolbar: Symfony’s Profiler shows form variables and submitted data.
Check Submitted Values:
$request->request->all(); // GET/POST data
$request->files->all(); // File uploads
use Symfony\Component\Form\DataTransformerInterface;
class SlugTransformer implements DataTransformerInterface
{
public function transform($value): ?string
{
return Str::slug($value);
}
public function reverseTransform($value): string
{
return Str::title($value);
How can I help you explore Laravel packages today?