azenet
azenet

Reputation: 379

Symfony DataTransformers with choice forms

I made a DataTransformer that is made to basically NOT the available choices with the submitted ones.

My issue is that my transformer receives either ['INFO' => 'INFO', 'WARN' => 'WARN'] or [0 => 'INFO', 1 => 'WARN']. This usually happens when all the checkboxes are unchecked (AKA the model data of the choice is ['INFO', 'WARN']) and that I check any and submit the form.

I am using Symfony 2.6.6.

Here is the form :

$choice = $builder->create('choice', 'choice', array(
    'choices' => array(
        'INFO' => 'INFO',
        'WARN' => 'WARN'
    ),
    'multiple' => true,
    'expanded' => true
);
$choice->addModelTransformer(new ReverseChoiceFieldTransformer($choice));
$builder->add($choice);

And here is the transformer :

class ReverseChoiceFieldTransformer implements DataTransformerInterface {
    private $choices;

    public function __construct(FormBuilderInterface $fbi) {
        $this->choices = $fbi->getOption('choices');
    }

    public function transform($value) {
        return $this->reverseTransform($value);
    }

    public function reverseTransform($value) {
        return $value === '' ? null : array_diff_key($this->choices, $value);
    }
}

Upvotes: 0

Views: 2593

Answers (1)

tftd
tftd

Reputation: 17042

I'm not quite sure why you'd want to use a DataTransformer in this case. They're used to simplify complex forms where the entered form data has to be transformed into something else. You might want to checkout the official documentation of Symfony, regarding Data Transformers.

Furthermore, your ReverseChoiceFieldTransformer class is not correctly implemented. The transform method is meant to transform an Object to a simple type like string,int,etc which is later displayed in the form as ViewData. On the contrary, the reverseTransform method is used to reverse transform the data from simple type back into the same Object. You can see this in the illustration below (taken from Symfony official docs)

Model and View Transformers

Having said that, I believe your approach to the problem might be wrong. It would probably better if you do this in the controller or in a form event. Here's an example of how to get the data in your controller class:

// MyFormType
class MyFormType extends AbstractType {

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('choice', 'choice', array(
            'choices' => array(
                'INFO' => 'Info label',
                'WARN' => 'Warn label',
                'CRIT' => 'Crit label',
            ),
            'multiple' => true,
            'expanded' => true,
            'required' => false,
            'mapped'   => false
        ))
        ->add('submit', 'submit', array(
            'attr' => array('class' => 'save'),
        ));;
    }

    public function getName() {
        return "my_form";
    }
}

// MyController
class MyController extends Controller {
    /**
     * @Route("/test")
     * @Template()
     */
    public function testAction(Request $request) {
        $form = $this->createForm(new MyFormType());
        $form->handleRequest($request);

        if($form->isValid())
        {
            // Get selected
            $selectedChoices = $form->get('choice')->getData();
            // outputs the checked values, i.e.
            // array (size=2)
            //    0 => string 'INFO' (length=4)
            //    1 => string 'WARN' (length=4)

            // All choices with their labels
            $allChoices = $form->get('choice')->getConfig()->getOption('choices');
            // outputs all choices, i.e.
            // array (size=3)
            //   'INFO' => string 'Info label' (length=10)
            //   'WARN' => string 'Warn label' (length=10)
            //   'CRIT' => string 'Crit label' (length=10)

            // All unchecked choices. Pay attention to the difference
            // in the arrays.
            $unchecked = array_diff(array_keys($allChoices), array_values($selectedChoices));

        }

        return array('form' => $form->createView());
    }
}


Follow up on comments:

You're partly correct. The transform method receives the model data which was used to build the form, while the reverseTransform method receives the data submitted to the form. Although in your case the received data in both cases is an array, the array itself contains different data structure:

  1. transform will receive:

    array (
        'INFO' => 'Info label',
        'WARN' => 'Warn label',
        'CRIT' => 'Crit label'
    )
    
  2. reverseTransform will receive the checked choices i.e.:

    array (
        0 => 'INFO',
        1 => 'CRIT'
    )
    


Although, I would go for a controller or research if this could be done with FormEvents, the following code should do the work:

class ReverseChoiceFieldTransformer implements DataTransformerInterface {
    private $choices;

    public function __construct(FormBuilderInterface $fbi) {
        $this->choices = $fbi->getOption('choices');
    }

    /**
     * The received data from the FormBuilder will be passed "AS IS"
     *
     * @param mixed $value
     * @return mixed
     */
    public function transform($value) {
        return $value;
    }

    /**
     * The received data from the submitted from will be modified. Doing
     * $form->get('choice')->getData() in the controller will return only
     * the unselected choices.
     *
     * @param mixed $value
     * @return null|array
     */
    public function reverseTransform($value) {
        return $value === '' ? null : array_diff(array_keys($this->choices),array_values($value));
    }
}

Upvotes: 3

Related Questions