简体   繁体   中英

Set form fields dynamically based on a url parameter in Symfony 3.3

I'm using Symfony 3.3. I have a form which needs to display some fields dynamically based on a parameter in the url.

The user is initially shown a form with only a subject field. After which, he chooses a subject and some fields in the form are either hidden or shown. Some of the fields are nullable so they don't have to be completed.

I've created a Form which looks like this.

As far as I'm concerned, it feels and looks like a mess. I'm very new to symfony and the documentaiton regarding this specific scenario either doesn't exist or I can't seem to find it.

const CONTACT_CHOICE_BLANK = 'blank';
const CONTACT_CHOICE_REGISTER = 'register';
const CONTACT_CHOICE_COMPANY = 'company';
const CONTACT_CHOICE_CONTACT = 'contact';

....

/**
 * @return string
 */
private function getChoice() {
    if($this->requestStack->getCurrentRequest()->query->has('subject')) {
        $subject = $this->requestStack->getCurrentRequest()->query->get('subject');
        switch($subject){
            case self::CONTACT_CHOICE_REGISTER:
                $default_choice = 'contact.form.select.option.' . self::CONTACT_CHOICE_REGISTER;
                break;
            case self::CONTACT_CHOICE_COMPANY:
                $default_choice = 'contact.form.select.option.' . self::CONTACT_CHOICE_COMPANY;
                break;
            case self::CONTACT_CHOICE_CONTACT:
                $default_choice = 'contact.form.select.option.' . self::CONTACT_CHOICE_CONTACT;
                break;

            case self::CONTACT_CHOICE_BLANK:
            default:
                $default_choice = 'contact.form.select.option.' . self::CONTACT_CHOICE_BLANK;
                break;
        }
        return $default_choice;
    }

    return self::CONTACT_CHOICE_BLANK;
}

/**
 * {@inheritdoc}
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $default_choice = $this->getChoice();

    $this->session->set('show_message', false);
    $this->session->set('show_email', false);
    $this->session->set('show_company', false);
    $this->session->set('show_email_pro', false);
    $this->session->set('show_company_spouse', false);

    $builder
        ->add('subject', ChoiceType::class, array(
            'choices' => array(
                'contact.form.select.option.' . self::CONTACT_CHOICE_BLANK    => self::CONTACT_CHOICE_BLANK,
                'contact.form.select.option.' . self::CONTACT_CHOICE_REGISTER => self::CONTACT_CHOICE_REGISTER,
                'contact.form.select.option.' . self::CONTACT_CHOICE_COMPANY  => self::CONTACT_CHOICE_COMPANY,
                'contact.form.select.option.' . self::CONTACT_CHOICE_CONTACT  => self::CONTACT_CHOICE_CONTACT,
            ),
            'label' => 'contact.form.select.subject',
            'required' => true,
            'data' => $default_choice,
        ))

        ->add('firstName', TextType::class, array('label' => 'contact.form.input.firstname'))
        ->add('familyName', TextType::class, array('label' => 'contact.form.input.familyname'))
        ->add('phoneNumber', TextType::class, array('label' => 'contact.form.input.phone'))
        ->add('contactReason', ChoiceType::class, array(
                'choices' => array(
                    'contact.form.select.option.advertising' => 'contact.form.select.option.advertising',
                    'contact.form.select.option.internet' => 'contact.form.select.option.internet',
                    'contact.form.select.option.member' => 'contact.form.select.option.member',
                    'contact.form.select.option.word' => 'contact.form.select.option.word',
                    'contact.form.select.option.other' => 'contact.form.select.option.other'),
                'label' => 'contact.form.select.reason'
            ))
        ->add('send', SubmitType::class, array('label' => 'contact.form.textarea.send'));


    $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($default_choice) {
        $form = $event->getForm();

        if($default_choice == 'contact.form.select.option.information_request') {
            $form->add('email', TextType::class, array(
                'label' => 'contact.form.input.email',
            ));
            $this->session->set('show_email', true);
        }

        if($default_choice == 'contact.form.select.option.business_membership_application') {
            $form->add('emailPro', TextType::class, array(
                'label' => 'contact.form.input.emailPro',
            ));
            $form->add('company', TextType::class, array(
                'label' => 'contact.form.input.company',
            ));
            $this->session->set('show_email_pro', true);
            $this->session->set('show_company', true);
        }

        if($default_choice == 'contact.form.select.option.registration_request') {
            $form->add('companySpouse', TextType::class, array(
                'label' => 'contact.form.input.companyspouse',
            ));
            $this->session->set('show_company_spouse', true);
        }

        if($default_choice == 'contact.form.select.option.registration_request' || $default_choice == 'contact.form.select.option.information_request') {
            $form->add('message', TextareaType::class, array(
                'label' => 'contact.form.textarea.message',
            ));
            $this->session->set('show_message', true);
        }
    });
}

In the controller, the function that handles this form looks like so:

public function contactAction(Request $request, Mailer $mailer) {

    $contact = new Contact();
    $form = $this->createForm(ContactType::class, $contact, ['allow_extra_fields' => true]);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $mailer->sendContactMail($form);

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

    return $this->render('WIVCoreBundle:Core:contact.html.twig', array(
        'form' => $form->createView(),
        'routes' => [
            'blank' => $this->generateUrl('wiv_core_contact', ['subject' => ContactType::CONTACT_CHOICE_BLANK]),
            'register' => $this->generateUrl('wiv_core_contact', ['subject' => ContactType::CONTACT_CHOICE_REGISTER]),
            'company' => $this->generateUrl('wiv_core_contact', ['subject' => ContactType::CONTACT_CHOICE_COMPANY]),
            'contact' => $this->generateUrl('wiv_core_contact', ['subject' => ContactType::CONTACT_CHOICE_CONTACT]),
        ],
        'default_route' =>  $this->generateUrl('wiv_core_contact'),
    ));
}

My question: Is there a better way to show/hide the fields? Perhaps something that doesn't feel like a complete mess?

I don't need hand holding, just some pointing in the right direction. Perhaps some parts of the documentation that I've missed.

Yes there is a better way to do this. You can use the form events to modify the elements of an form. There is also a cookbook entry, which describes this feature: http://symfony.com/doc/current/form/dynamic_form_modification.html

I also strongly recommend the slides from Bernhard Schussek (the creator of this component):

https://speakerdeck.com/webmozart/symfony2-form-tricks

I am not sure if I get your question ,I will provide an answer and feel free to comment if I got it wrong.

Why don't you set the subject in your contact entity within the controller :

public function contactAction(Request $request, Mailer $mailer) {

    $contact = new Contact();
    $contact->setSubject($request->query->get('subject');
    ..... 
}

When passing the contact entity to the form it already have the subject field, then in the PRE_SET_DATA event you check on the subject field of contact entity:

 $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($default_choice) {
    $form = $event->getForm();
    $contact = $event->getData();
    if ($contact && $contact->getSubject() == 'value1' ) {
       $form->add(...)
     // do your magic here 
    }));
    }

Source : http://symfony.com/doc/2.8/form/dynamic_form_modification.html

Instead of setting the Form fields, you can use your entity (or DTO) to do the work. To make it even easier, you can use Symfony's PropertyAccess Component.

Something like this:

public function contactAction(Request $request, Mailer $mailer)
{
    $contact = new Contact();
    $contact->fromArray($request->query->all());

    $form = $this->createForm(ContactType::class, $contact, ['allow_extra_fields' => true]);

    //...
}


class Contact
{
    public function fromArray(array $properties) : void
    {
        $propertyAccessor = PropertyAccess::createPropertyAccessor();

        foreach ($properties as $key => $value) {
            if (! $propertyAccessor->isWritable($this, $key)) {
                continue;
            }

            $propertyAccessor->setValue($this, $key, $value);
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM