'Symfony/Form add attribute based on the data

In one of my custom form types I need to add a custom HTML attribute to the field. However that attribute is based on the data. So I added an event handler but I'm not sure what I should do in it.

$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
    $data = $event->getData();
    // not sure what to do here.
};

Maybe it should be done somewhere else. Please keep in mind that for my use case I need the data initially set to the form, not the submitted data.

EDIT: I've been asked for some more details of what I'm trying to achieve. Basically I need to put the initial data from database (which are available in the PRE_SET_DATA event) into a data-* HTML attribute so that javascript could use it.

UPDATE: Even after several months there is no good answer so I assume it is currently impossible to solve this.



Solution 1:[1]

You can replace your old form-element with new one:

$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
    $data = $event->getData();
    $form = $event->getForm();
    $val = $data['some_field'];
    $options = $form->get('existing_field_name_to_replace')->getConfig()->getOptions();
    $options['attr']['your-attr'] = $val;
    $form->add('existing_field_name_to_replace', 'type', $options);
};

$form->add() replaces previously defined form field. But you can also use $form->remove() and then $form->add().

Solution 2:[2]

If that's an HTML attribute, you can do it only by editing your view:

<div {% if form.DATA_FIELD.vars.value == 'foo' %}disabled{% endif %}>

In that case, the <div> will be disabled if the data has value "foo" in DATA_FIELD. If your logic is more complex than a simple check of data attribute, you might want to create a twig extension.

Solution 3:[3]

The solution is to implement the buildView method of your form type. This method receives the FormView argument which may be used to add any attributes and manipulate the tag class if you need it. It also receives the Form instance so you can fetch the data and implement any logic you want.

class MyCustomForm extends AbstractType {

    public function buildForm(FormBuilderInterface $builder, Array $options = []) {
        // Build your form here
    }

    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        $data = $form->getData();
        // Checking if data exists as the form may be empty
        if($data and $data->isSomething()) {
            $view->vars['attr']['data-foo'] = $data->getBar();
            $view->vars['attr']['class'] .= $data->isBeautiful() ? ' makeBeautiful' : '';
        }
        
        // You may want to access the child views as well
        if($data and $data->hasBeautifulChild()) {
            $view->children['child']->vars['attr']['class'] .= ' makeBeautiful';
        }
    }

}

That's it! Symfony call the buildView before rendering your form automatically and use the adjusted view to draw the whole thing.

Solution 4:[4]

I would not do something as straight forward via a listener for a number of reasons.

  1. its complicated to debug/write
  2. every listener you add slows down the framework
  3. its overkill for most cases

The best way that I can see is to add it via the controller. As you've provided no use case, I'll make one up for the purposes of illustration.


Use case

You have a field in a form that needs to be either disabled, or enabled depending on whether or not its coping with a new entity, or an edited one.


Controller

public function doSomethingAction(Request $request) {
    
    // ......

    $edit = $this->areWeEditing(); // or whatever data youre trying to inject

    // options to send to the form
    $options = [
        'disable_field' => $edit, // <--- put your data in here.
    ];
    $entity = new Something();
    $form = $this->createForm( SomethingType::class, $entity, $options );

    $form->handleRequest( $request );

    if ( $form->isValid() ) {
        //... do something
    }

    // .....
}

SomethingType.php

class SomethingType extends AbstractType {
    public function buildForm( FormBuilderInterface $builder, array $options ) {
        $builder->add( 'name', TextType::class, [
            'label'    => 'Name',
            'required' => true,
            'disabled' => $options[ 'disable_field' ],  // <---- use the $option here.
        ] )->add( 'email', EmailType::class, [
             'required' => true,
        ] );
    }

    public function configureOptions( OptionsResolver $resolver ) {
        $resolver->setDefaults( [
            'data_class'     => Something::class,  // whatever entity class youre mapping to
            'disabled_field' => false,  // you need to set this to tell symfony its an allowed option. Its name needs to match the incoming array key.  If the array key isnt present, this default will be used instead.
        ] );
    }
}

The data you pass in can be used either as the array key, or value, or both depending on what youre trying to achieve.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Terenoth
Solution 3 Sergey
Solution 4 Community