'add model transformer for a certain form field in form events

The only way that works I have found is to add model transformer in buildForm method of a form type, as these codes below:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $transformer = new IssueToNumberTransformer($entityManager);
    $builder->add(
        $builder->create('issue', 'text')->addModelTransformer($transformer)
    ); 
}

But I have a form field which displays when a another form field has a valid value, so I'd rather create the form field in FormEvent::PRE_SET_DATA event.

1. one wrong way

->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder) {
     /** @var $order \VMSP\OrderBundle\Entity\OrderInterface */
     $order = $event->getData();
     $form  = $event->getForm();
     /** @var $serviceType \VMSP\StoreBundle\Entity\ServiceType */
     $serviceType = $order->getServiceType();

     //only home service needs user's address
     if ($serviceType && $serviceType->getType() == ServiceType::TYPE_HOME_SERVICE) {
         //won't work
         $form->add(
             $builder->create('address','hidden')
                     ->addModelTransformer($this->addressTransformer),
             array(
                 'label' => 'vmsp_order.contact.form.address',
             )
         );
    }
}

somebody suggested

$form->add(
   $builder->create('address', 'hidden')
           ->addModelTransformer($this->addressTransformer),
   array( 'label' => 'vmsp_order.contact.form.address')
);

unfortunately, it throws this error:

Expected argument of type "string, integer or Symfony\Component\Form\FormInterface", "Symfony\Component\Form\FormBuilder" given

2. another wrong way

if ($serviceType && $serviceType->getType() == ServiceType::TYPE_HOME_SERVICE) {
    $form->add(
        'address', 
        'hidden', 
        array('label' => 'vmsp_order.contact.form.address')
    );

    $form->get('address')
         ->getConfig()
         ->addModelTransformer($this->addressTransformer);
}

got error:

FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.

I list the two wrong ways above, because I find lots of post saying these ways are right, of course, they are not. this post is a same question like symfony2-form-events-and-model-transformers , but that answer is not what I need, so my question is, any way to add model transformer in form events for a certain form field?



Solution 1:[1]

Your "1. one wrong way" doesn't work because $builder->addModelTransformer() returns a FormConfigBuilderInterface but $form->add() expects a FormInterface (see your error message).

To make it work, just add getForm() :

$form->add(
    $builder->create('address','hidden')
        ->addModelTransformer($this->addressTransformer)
        ->getForm()   // Creates the form
        ...

Solution 2:[2]

I recommend to do it in reverse mode. Configurate your hidden address field and remove it later if neccessary:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $transformer = new IssueToNumberTransformer($entityManager);
    $builder->add(
        $builder->create('issue', 'text')->addModelTransformer($transformer)
    )->add('address','hidden')
     ->addModelTransformer($this->addressTransformer);
}

Then, check in listener if the hidden field should be removed:

 ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder) {
     /** @var $order \VMSP\OrderBundle\Entity\OrderInterface */
     $order = $event->getData();
     $form  = $event->getForm();
     /** @var $serviceType \VMSP\StoreBundle\Entity\ServiceType */
     $serviceType = $order->getServiceType();

     //only home service needs user's address
     if (!$serviceType || $serviceType->getType() != ServiceType::TYPE_HOME_SERVICE) {
         //will work
         $form->remove('address');
    }
}

I am wondering why the first wrong way does not working? Which error appears?

Solution 3:[3]

Thanks to @gblock I managed to get the job done to add a ModelTransformer in a form event. But I'm going to provide a better example.

I needed for instance to set these extra options before it worked: I needed the DateTimeToStringTransformer in my Form Event because the date format relied on data from the entity

'data_class' => null,
'auto_initialize' => false,

Short example:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder) {
        $form = $event->getForm();

        $timeStampField = $builder->create('timestamp', TextType::class, [
            'data_class' => null,
            'auto_initialize' => false,
        ])
            ->addModelTransformer(new DateTimeToStringTransformer(null, null, 'd-m-Y H:i'))
            ->getForm()
        ;

        $form->add($timeStampField);
    });
}

Long example:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        // Some other fields not relying on the entity
    ;

    // Important! Pass the builder into the event listener with -> "use ($builder)"
    $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder) {
        $form = $event->getForm();
        $entity = $event->getData() ?: new MyEntity();

        // In this example: set some stuff related to entity being "editable"
        $isEditable = $entity->isEditable();

        // In this example: we want different timestamp for an un-editable entity
        $dateTimeFormat = $isEditable ? 'd-m-Y H:i' : 'd-m-Y H:i:s';

        $timeStampField = $builder->create('timestamp', TextType::class, [
            'label' => 'time',
            'data' => $entity->getDate(),

            // For this example: we want to have our un-editable entities to be "mapped: false"
            'mapped' => $isEditable,

            /**
             * These options are important in this case:
             * data_class: null
             * auto_initialize: false
             */
            'data_class' => null,
            'auto_initialize' => false,
        ])
            ->addModelTransformer(new DateTimeToStringTransformer(null, null, $dateTimeFormat))
            // Important line: In FormEvent we really need it to be "Form" and not "FormBuilder"
            ->getForm()
        ;

        $form->add($timeStampField);
    });
}

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 gblock
Solution 2 Rawburner
Solution 3