The document provides an overview of the Symfony Form component, including basic usage, validation, custom form types, events, data transformers, form type extensions, and rendering. It demonstrates how to create and handle forms, add validation, dynamically modify forms using events, transform data between representations, extend existing form types, and customize form rendering.
10. Basic usage (Validation on the object)
use SymfonyComponentValidatorConstraints as Assert;
class MyObject
{
/**
* @AssertNotBlank
* @AssertLength(
* min=10,
* minMessage="The comment have to be useful"
* )
*/
private $comment;
// Required methods
public function getComment();
public function setComment($comment);
}
11. Basic usage (Get validation errors)
...that's rendered for you!
But if you want access to them...
$form->handleRequest($request);
if (!$form->isValid()) {
$errors = form->getErrors();
// `$errors` is an array of `FormError` objects.
}
14. Custom type definition
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('comment', 'textarea')
;
}
}
15. Using the Form Type
public function createAction(Request $request)
{
$form = $this->createForm(new MyFormType());
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
// Do what ever you want with the data...
$comment = $data['comment'];
}
return [
'form' => $form->createView(),
];
}
16. Form Type options
class MyFormType extends AbstractType
{
//! Replace `setDefaultOptions` since Symfony 3.0 /!
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired(['my-custom-option']);
$resolver->setDefaults(array(
'data_class' => 'AppModelObject',
));
}
}
17. Form Type as a service
class GenderType extends AbstractType
{
private $genderChoices;
public function __construct(array $genderChoices)
{
$this->genderChoices = $genderChoices;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'choices' => $this->genderChoices,
));
}
}
18. Form Type as a service
Register the form type
<service id="app.form.type.gender" class="AppFormTypeGenderType">
<argument>%genders%</argument>
<tag name="form.type" />
</service>
Use the form type
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('gender', GenderType::class)
;
}
}
20. Form workflow
It dispatches different events
while handling the requests.
» PRE_SET_DATA
» POST_SET_DATA
» PRE_SUBMIT
» SUBMIT
» POST_SUBMIT
21. The name field only for a new product
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$product = $event->getData();
$form = $event->getForm();
// The product name is only updatable for a new `Product`
if (!$product || null === $product->getId()) {
$form->add('name', TextType::class);
}
});
}
22. Event subscribers
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->addEventSubscriber(new AddNameFieldSubscriber());
}
23. An event subscriber
class AddNameFieldSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(FormEvents::PRE_SET_DATA => 'preSetData');
}
public function preSetData(FormEvent $event)
{
$product = $event->getData();
$form = $event->getForm();
if (!$product || null === $product->getId()) {
$form->add('name', TextType::class);
}
}
}
24. List based on another field
1. Initial Form Type
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('sport', EntityType::class, array(
'class' => 'AppBundle:Sport',
'placeholder' => '',
))
;
}
25. List based on another field
2. Our form modifier
$formModifier = function (FormInterface $form, Sport $sport = null) {
$positions = null === $sport ? array() : $sport->getAvailablePositions();
$form->add('position', EntityType::class, array(
'class' => 'AppBundle:Position',
'placeholder' => '',
'choices' => $positions,
));
};
26. List based on another field
3. The listeners
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($formModifier) {
$data = $event->getData();
$formModifier($event->getForm(), $data->getSport());
});
$builder->get('sport')->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$sport = $event->getForm()->getData();
// since we've added the listener to the child, we'll have to pass on
// the parent to the callback functions!
$formModifier($event->getForm()->getParent(), $sport);
});
28. Normalization flow
1. Model data. Our object returned
by getData().
2. Internal representation, mostly
our model data. Almost never
used by the developer.
3. View data. The data structure
sent to submit().
29. Using the CallbackTransformer
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description', TextareaType::class);
$builder->get('description')->addModelTransformer(new CallbackTransformer(function ($originalDescription) {
// transform <br/> to n so the textarea reads easier
return preg_replace('#<brs*/?>#i', "n", $originalDescription);
}, function ($submittedDescription) {
// remove most HTML tags (but not br,p)
$cleaned = strip_tags($submittedDescription, '<br><br/><p>');
// transform any n to real <br/>
return str_replace("n", '<br/>', $cleaned);
}));
}
30. An integer to an object
1. Our task Form Type
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', TextareaType::class)
->add('issue', TextType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppModelTask'
));
}
// ...
}
31. An integer to an object
2. The transformer
class IssueToNumberTransformer implements DataTransformerInterface
{
private $issueRepository;
public function __construct(IssueRepository $issueRepository) {
$this->issueRepository = $issueRepository;
}
// Two methods to implement...
public function transform($issue);
public function reverseTransform($issueNumber);
}
32. An integer to an object
3. From model to view
public function transform($issue)
{
return null !== $issue ? $issue->getId() : '';
}
33. An integer to an object
3. From view to model
public function reverseTransform($issueNumber)
{
if (empty($issueNumber)) {
return;
}
if (null === ($issue = $this->issueRepository->find($issueNumber))) {
throw new TransformationFailedException(sprintf('An issue with number "%s" does not exist!', $issueNumber));
}
return $issue;
}
34. An integer to an object
4. Voilà!
class TaskType extends AbstractType
{
private $issueRepository;
public function __construct(IssueRepository $issueRepository) {
$this->issueRepository = $issueRepository;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->get('issue')->addModelTransformer(new IssueToNumberTransformer($this->issueRepository));
}
}
36. An extension class
class IconTypeExtension extends AbstractTypeExtension
{
public function getExtendedType()
{
return ChoiceType::class;
}
// We can now declare the following methods...
public function configureOptions(OptionsResolver $resolver);
public function buildForm(FormBuilderInterface $builder, array $options);
public function buildView(FormView $view, FormInterface $form, array $options);
public function finishView(FormView $view, FormInterface $form, array $options)
}
37. An optional extra icon
class IconTypeExtension extends AbstractTypeExtension
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'icon' => null,
]);
}
}
38. An optional extra icon
class IconTypeExtension extends AbstractTypeExtension
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['icon'] = $options['icon'];
}
}
You'll need to extend the choice_widget block using a form
theme, so you can display an icon when the local icon variable is
defined.
43. Creating a form theme
Using Twig blocks.
» [type]_row
» [type]_widget
» [type]_label
» [type]_errors
If the given block of type is not found, it will use the parent's
type.
44. FormType's buildView
class GenderType extends AbstractType
{
private $genderChoices;
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['genders'] = $this->genderChoices;
}
}
46. FormType's finishView
Called when the children's view is completed.
public function finishView(FormView $view, FormInterface $form, array $options)
{
$multipart = false;
foreach ($view->children as $child) {
if ($child->vars['multipart']) {
$multipart = true;
break;
}
}
$view->vars['multipart'] = $multipart;
}
47. Creating a form without name
1. The difference?
Your form properties won't be namespaced
Example: comment instead of my_form[comment]
You might need/want to use it for:
- Legacy applications compatibility
- APIs (with the FOS Rest Body Listener)
48. Creating a form without name
2. How?
private $formFactory;
public function __construct(FormFactoryInterface $formFactory)
{
$this->formFactory = $formFactory;
}
public function createAction(Request $request)
{
$form = $this->formFactory->createNamed(null, new MyFormType());
$form->handleRequest($request);
// ...
}