Symfony4/5 forms Cheatsheet
FormType / Configuration
-
// /src/Form/Type/FooFormType.php namespace App\Form\Type; class fooFormType extends AbstractType { // Inject a service protected $myService; public function __construct(MyService $myService) { $this->myService = $myService; } // Configure options public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'action' => '/my/url/', 'csrf_protection' => false, 'method' => 'GET', // bind to a data class (form binding returns this object as result) 'data_class' => Foo::class, // pass custom options to the form 'myOption => '', ]); } public function buildForm(FormBuilderInterface $builder, array $options): void { // Set a dynamic action $builder->setAction('/foo/ . $options['myOption']; $builder->add('first_name', TextType::class, [ 'required' => false, 'label' => 'First name', 'attr' => ['placeholder' => 'Enter your first name'], ]); $builder->add('secret', HiddenType::class, [ 'empty_data' => 'default', 'required' => false, 'label' => 'Secret', ]); // Set value for hidden $builder->setData(['bereich' => $options['bereich']]); // Use service $this->myService->doSomething(); // Use options $options['myOption']; } }
Set Placeholder for TextType Field
Why not support 'placeholder' option like in ChoiceType?
https://github.com/symfony/symfony/issues/45214
-
$builder->add('location', TextType::class, [ 'attr' => ['placeholder' => 'Town or State)'], ]);
Form Builder
With a lot of options
-
$formBuilder = $formFactory->createNamedBuilder( 'filterForm', FormType::class, null, ['csrf_protection' => false, 'attr' => ['id' => 'filter-form'] ]); $formBuilder->setMethod('GET');
Controller / Action / Usage
-
namespace App\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; class myController extends AbstractController { public function myAction(Request $request, FormFactoryInterface $formFactory ) { // With data class 'Foo' and options $form = $this->createForm(FooFormType::class, $foo, [ 'myOption' => 'some value', ]); $form->handleRequest($request); // Or if you don't want to clear missing fields $form->submit($request->get($form->getName()), false); // Check for valid form if (!($form->isSubmitted() && $form->isValid())) { // Debugging only. Giving true to ->getErrors() is very important!!! dd($form->isSubmitted(), $form->isValid(), $form->getErrors(true)); throw new \RuntimeException('Form is not submitted or not valid'); } return $this->renderForm('myTemplate.html.twig', [ 'form' => $form, ]); } }
Do not clear missing fields
We have a form object and set some defaults, then we create the form with it.
Does not work because it clears missing fields:
-
$form->handleRequest($request);
Use instead:
-
$form->submit($request->get($form->getName()), false);
Use custom prefix or without prefix / form name
With custom form name / prefix
If set to empty string '' -> <input name="first_name"> instead of <input name="foo[first_name]">
-
use Symfony\Component\Form\FormFactoryInterface; public function myAction(Request $request, FormFactoryInterface $formFactory ) { $form = $this->formFactory->createNamed('', FooType::class); }
Wishlist
- Allow to change values in bound form (e.g. update "offset" pagination field)
- Allow to set form name (field prefix) in config:
-
public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'name' => 'my_prefix',
- Renders: <input name="my_prefix[first_name]">
-
- Support 'placeholder' option in all suitable form field types (like in ChoiceType)
- Form Twig functions are cumbersome for custom attributes like CSS classes -> no IDE autocompletion e.g. for Bootstrap
- Allow to get the form as http url / query similar to JS
-
new URLSearchParams(newFormData(formElement))
- or PHP http_build_query()
-
$form->getHttpParams()
-
-
Handle CKEditor Upload Data
- src/Form/Type/CKEditorFormType.php
-
<?php namespace App\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints\File; class CKEditorFormType extends AbstractType { public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'csrf_protection' => false, ]); } public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('upload', FileType::class, [ 'required' => false, 'constraints' => [ new File([ 'maxSize' => '2048k', 'mimeTypes' => [ 'image/jpeg', 'image/x-png', ], 'mimeTypesMessage' => 'Please upload a valid image', ]) ], ]); } }
-
- Controller:
-
public function CkEditorUpload(Request $request, FormFactoryInterface $formFactory) { //create form with empty name/prefix to handle plain POST variables $form = $this->formFactory->createNamed('', CKEditorFormType::class); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $file = $form->get('upload')->getData(); ... validate and process file }
-
Use validator constraints for file upload
- Controller:
-
// Inject ValidatorInterface $validator /* @var UploadedFile $file */ $file = $request->files->get('upload'); if ($file) { $violations = $validator->validate($file, new Image([ 'maxSize' => '8192k', 'mimeTypes' => [ 'image/jpeg', 'image/x-png', ], ])); if (count($violations)) { $response = new Response($violations[0]->getMessage(), 400); return $response; } $originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); $safeFilename = $slugger->slug($originalFilename); $newFilename = $safeFilename . '-' . uniqid() . '.' . $file->guessExtension(); try { $uploadDir = $kernel->getProjectDir() . '/public/uploads'; $file->move( $uploadDir, $newFilename); } catch (FileException $e) { $response = new Response('Error: uploaded file could not be saved.', 400); return $response; }
-
Custom Validator Constraints
Constraints also work well for conditional validation.
e.g. cross check if a username exists in the other of two tables:
- src/Validator/Constraints/UniqueUsername.php
-
<?php namespace App\Validator\Constraints; use Symfony\Component\Validator\Constraint; /** * @Annotation */ class UniqueUsername extends Constraint { public $message = 'The username "{{ value }}" is already in use in table {{ entity }}.'; }
-
-
src/Validator/Constraints/UniqueUsernameValidator.php
-
<?php namespace App\Validator\Constraints; use App\Entity\Foo; use App\Entity\Bar; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Doctrine\ORM\EntityManagerInterface; /** * Cross check for Bar <-> Foo if username exists in other table and if so add a validation error */ class UniqueUsernameValidator extends ConstraintValidator { private $entityManager; public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; } public function validate($value, Constraint $constraint) { $username = $value; $entity = $this->context->getObject(); // dd($username, $entity, get_class($entity)); if ("App\Entity\Bar" == get_class($entity)) { $Foo = $this->entityManager->getRepository(Foo::class)->findOneBy(['username' => $username]); if ($Foo) { // If the check fails, use the following method to add a validation error to the field $this->context->buildViolation($constraint->message) ->setParameter('{{ value }}', $value) ->setParameter('{{ entity }}', 'Foo') ->addViolation(); } } if ("App\Entity\Foo" == get_class($entity)) { $Bar = $this->entityManager->getRepository(Bar::class)->findOneBy(['username' => $username]); if ($Bar) { // If the check fails, use the following method to add a validation error to the field $this->context->buildViolation($constraint->message) ->setParameter('{{ value }}', $value) ->setParameter('{{ entity }}', 'Bar') ->addViolation(); } } } }
-
-
src/Entity/Foo.php
-
/** * @var string|null * * @ORM\Column(name="username", type="string", length=30, nullable=true, unique=true) * * Cross check if username already exists in entity "Bar": * @UniqueUsername */ private $username;
-
-
Repeat the same for entity "Bar"