Skip to content

Instantly share code, notes, and snippets.

@Raphhh
Last active August 2, 2023 13:45
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Raphhh/91a1eae22f76f92c00b806b3644a2a1e to your computer and use it in GitHub Desktop.
Save Raphhh/91a1eae22f76f92c00b806b3644a2a1e to your computer and use it in GitHub Desktop.
Symfony form: set options according to the data
<?php
namespace AppBundle\Form\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class ContextualOptionsFormListener implements EventSubscriberInterface
{
/**
* @var callable
*/
private $callback;
/**
* The callback will be called for each child of the listened form.
* The callback will receive the following params:
* - FormEvent $event
* - $name name of the child
* - array $options the initial options of the child
* The callback must return an array with the final options of the child.
*
* @param callable $callback
*/
public function __construct(callable $callback)
{
$this->callback = $callback;
}
/**
* @return array The event names to listen to
*/
public static function getSubscribedEvents()
{
return [
FormEvents::PRE_SET_DATA => 'preSetData',
];
}
/**
* @param FormEvent $event
*/
public function preSetData(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
if (null === $data) {
return;
}
$children = $form->all();
foreach ($form as $name => $child) {
$form->remove($name);
}
foreach ($children as $name => $child) {
$form->add(
$name,
$child->getConfig()->getType()->getInnerType(),
call_user_func($this->callback, $event, $name, $child->getConfig()->getOptions())
);
}
}
}
<?php
namespace AppBundle\Model;
class Example
{
public $value;
public function isDisabled()
{
return $this->value === 'disabled';
}
}
<?php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use AppBundle\Model\Example;
use AppBundle\Model\Form\Type\ExampleType;
class ExampleController extends Controller
{
/**
* @Route("/")
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function indexAction()
{
$example = new Example();
$example->value = 'disabled';
$form = $this->createForm(ExampleType::class, $example);
...
}
}
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use AppBundle\Form\EventListener\ContextualOptionsFormListener;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
class ExampleType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('value');
$builder->addEventSubscriber(new ContextualOptionsFormListener(
function (FormEvent $event, $name, array $options) {
if ($name === 'value') {
//the "value" field will be conditionnaly disabled according to Example instance.
$options['disabled'] = $event->getData()->isDisabled();
}
return $options;
}
));
}
}
@Raphhh
Copy link
Author

Raphhh commented Dec 1, 2016

The goal:

You want to set some options conditionnally, according to the data given.

The problem:

  1. The data given to the FormBuilder is not available in the method buildForm (You can retrieve it with $options['data'], but only in the parent form, not in the children).
  2. The config of the form is early locked, and it is not possible to use events such PRE_SET_DATA: https://knpuniversity.com/screencast/question-answer-day/symfony2-conditionally-required-field#a-solution-that-doesn-t-work-event-listeners

The solution here

We loop on each child and reset it with a callback that can modify the options according to the data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment