zoore
zoore

Reputation: 330

Symfony form EntityType::class - How use it to edit data in form (3.2.13)

A dropdown selection menu in a Form is available by using a EntityType form type. It is usefull if you add data. But when you try to edit data with the same FormType class, your $request data are overwritten by your EntityType.

How use the same FormType class when editing the data? (eg. editAction in controller) How pass Request $request data to FormType fields as "defaults, or selected" for element EntityType::class in FormBuilder? Is there something in $builder->add() method I can use like if(['choice_value'] !=== null ) xx : yy?

How to get something like html select selected, from Request object and pass it to xxxFormType class and bind to right EntityType::class element there.

<select>
  <option value="volvo">Volvo</option>
  <option value="vw">VW</option>
  <option value="audi" selected>Audi</option>
</select> 

I've looked at EntityType Field, How to Dynamically Modify Forms Using Form Events and lots of StackOverflow posts, but can't find proper solution.

Controller:

public function editProdPackageAction(Request $request, ProdPackage $prodPackage)
{
    $form = $this->createForm(ProdPackageFormType::class, $prodPackage);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $prodPackage = $form->getData();

        $em = $this->getDoctrine()->getManager();
        $em->persist($prodPackage);
        $em->flush();

        $this->addFlash('success', 'MSG#011E');

        return $this->redirectToRoute('admin_package_list');
    }


    return $this->render(':admin/forms/ProdPackage:edit.html.twig',
        [
            'ppg' => $form->createView(),
        ]
    );
}

Formtype:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add(
            'idCatBig',
            EntityType::class,
            [
                'label'         => '* category',
                'class'         => 'AppBundle\Entity\ProdCategoryBig',
                'choice_value'  => 'id',
                'choice_label'  => 'shortName',

                'multiple'      => false,
                'expanded'      => false,
            ]
        )
        ->add(
            'dateStart',
            DateType::class,
            [
                'label'                     => '* some date',
                'data'  => new \DateTime('now'),
                'choice_translation_domain' => true,
            ]
        )
        ->add(
            'dateEnd',
            DateType::class,
            [
                'label' => '* till',
                    'data'  => new \DateTime('now'),
            ]
        )
        ->add(
            'packageName',
            TextType::class,
            [
                'label' => '* package',
                'attr'  => ['placeholder' => 'default Name'],
                'data'  => 'your default value',
            ]
        )

Upvotes: 2

Views: 4251

Answers (3)

Stephan Vierkant
Stephan Vierkant

Reputation: 10144

Problem

You are overriding an object by using the data option.

https://symfony.com/doc/3.2/reference/forms/types/entity.html#data:

( ! ) The data option always overrides the value taken from the domain data (object) when rendering. This means the object value is also overriden when the form edits an already persisted object, causing it to lose its persisted value when the form is submitted.

Solution 1: use the controller

So the solution is pretty simple: don't use data. Instead, set default values in your addProdPackageAction action (or whatever it is called):

public function editProdPackageAction(Request $request)
{
    $prodPackage = new ProdPackage();
    $prodPackage->setDateEnd(new Datetime('now'));

    //example: use Request or other controller methods to modify your entity
    if ($this->getUser()->hasRole('ROLE_ADMIN')) {
        $prodPackage->setCreatedByAdmin(true);
    }

    //your form
}

Solution 2: use your entity constructor

Alternatively, you can use your entity constructor method:

class ProdPackage
{
    //your attributes
    private $dateEnd;

    public function __construct()
    {
        $this->dateEnd = new Datetime('now');
    }
}

Upvotes: 2

zoore
zoore

Reputation: 330

Found another interesting solution

in formTypeclass at $builder

->add(
                    'trBegin',
                    DateTimeType::class,
                    [
                        'label'       => 'Tourney Begins',
                        'required'    => true,
                        'date_widget' => 'single_text',
                        'time_widget' => 'single_text',
                        'date_format' => 'dd.MM.yyyy',
                    ]
                )

and continuing building form add:

        $builder->get('trBegin')->addModelTransformer(
            new CallbackTransformer(
                function ($value) {
                        if (!$value) {
                                return new \DateTime('now + 60 minutes');
                        }

                        return $value;
                },
                function ($value) {
                        return $value;
                }
            )
        );

it sets default date at moment when form is created. This method is very usefull also for EntityType object, where you can pass id of field in form and get selected your current real choice from database (not all list from the begining) very useful when using EntityField also for editing forms

 $builder->get('productCategory')
        ->addModelTransformer(new CallbackTransformer(
            function ($id) {
                if (!$id) {
                    return;
                }

                return $this->em->getRepository('AppBundle:ProductCategory')->find($id);
            },
            function($category) {
                return $category->getId();
            }
        ));

Upvotes: 0

Joe Yahchouchi
Joe Yahchouchi

Reputation: 626

This is what I do in my form, I set a "pre set data listener" to check whether or not this is an edit or a create

function buildForm(FormBuilderInterface $builder, array $options) {
    $builder->add(
    'dateStart',
    DateType::class,
    [
    'label' => '* some date'
    //I don't set a data field here because it is an edit
    'choice_translation_domain' => true,
    ]
    )

    $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
        $data = $event->getData();
        $form = $event->getForm();
        if (!$data || null === $data->getId()) {
            //if the entity does not have an ID it means that this is a new entity not an edit. because all edits have been in the database and have an id
            $builder->add(
            'dateStart',
            DateType::class,
            ['label' => '* some date',
            'data' => new \DateTime('now'), //this is a create so I override my previous setting and set a default data
            'choice_translation_domain' => true,]
            )
        }
    });
}

I mainly use this trick to change form fields from required to non required between edits and creates for password fields for example,and sometimes if there is something exceedingly complicated. To change data, honestly, setting default data in constructor is cleaner as Stephan suggests

Upvotes: 2

Related Questions