A
A
Alexey Verkhovtsev2019-05-06 23:17:37
symfony
Alexey Verkhovtsev, 2019-05-06 23:17:37

How to dynamically load data into a symfony form select?

There is a DTO with data

<?php

namespace App\DTO\PaymentSystem;

use App\DTO\DataTransferObject;

class PaymentCreateDTO implements DataTransferObject
{
    public $stripeProductId;

    public $stripePlanId;

    public $stripeSourceName;

    public $stripeCoupon;

    public $stripeToken;

    public $stripeSource;

    public $renew;

    public $saveSource;

    public $freeTrial;
}

there is a mapper
Here is a stripped down version of the form class
public function buildForm(
        FormBuilderInterface $builder,
        array $options
    ): void {
        parent::buildForm($builder, $options);

        $builder
            ->add('stripeProductId', EntityType::class, [
                'class' => StripeProduct::class,
                'query_builder' => function (EntityRepository $er) {
                    return $er
                        ->createQueryBuilder('o')
                        ->andWhere('o.active = :active')
                        ->setParameter('active', true);
                },
                'choice_label' => 'name',
                'choice_attr' => function (StripeProduct $stripeProduct) {
                    return [
                        'data-product-bugs-count' =>
                            $stripeProduct->product()->bugsCount(),
                        'data-product-name' =>
                            $stripeProduct->name()
                    ];
                }
            ])
            //->add('stripePlanId', ChoiceType::class)
            ->add('renew', CheckboxType::class, [
                'required' => false
            ])
            ->add('saveSource', CheckboxType::class, [
                'required' => false
            ])
            ->add('freeTrial', CheckboxType::class, [
                'required' => false
            ])
            ->add('stripeToken', HiddenType::class)
            ->add('stripeSource', HiddenType::class)
            ->add('stripeCoupon', TextType::class)
            ->add('stripeSourceName', TextType::class)
            ->setDataMapper(new PaymentCreateMapper2());

        $builder
            ->get('stripeProductId')
            ->addModelTransformer(new CallbackTransformer(
                function ($value) {
                    return $value;
                },
                function (StripeProduct $value) {
                    return $value->getId();
                }
            ));

        $formModifier = function (FormInterface $form, string $stripeProductId = null) {
            $form->add('stripePlanId', EntityType::class, [
                'class' => StripePlan::class,
                'query_builder' => function (EntityRepository $er) use ($stripeProductId) {
                    return $er
                        ->createQueryBuilder('o')
                        ->andWhere('o.active = :active')
                        ->andWhere('o.stripeProduct = :stripeProduct')
                        ->setParameter('active', true)
                        ->setParameter('stripeProduct', $stripeProductId);
                },
                'choice_label' => 'name',
                'choice_attr' => function (StripePlan $stripePlan) {
                    return [
                        'data-plan-name' =>
                            $stripePlan->name(),
                        'data-plan-price' =>
                            $stripePlan->price(),
                        'data-plan-free_trial' =>
                            $stripePlan->trialPeriodDays()
                    ];
                }
            ]);
        };

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

                /** @var PaymentCreateDTO $data */
                $data = $event->getData();
                
                $formModifier($form, $data->stripeProductId);
            }
        );

        $builder->get('stripeProductId')->addEventListener(
            FormEvents::POST_SUBMIT,
            function (FormEvent $event) use ($formModifier) {
                //dd(2);
                // It's important here to fetch $event->getForm()->getData(), as
                // $event->getData() will get you the client data (that is, the ID)

                /** @var PaymentCreateDTO $data */
//                $data = $event->getData();
//                dd($data);
                $stripeProductId = $event->getData();

                //dd($stripeProductId);

                // since we've added the listener to the child, we'll have to pass on
                // the parent to the callback functions!
                $formModifier($event->getForm()->getParent(), $stripeProductId);
            }
        );
    }

parent::buildForm($builder, $options); - this is a call from the base class, it's just validation so that they can't slip the wrong object into the builder
public function buildForm(
        FormBuilderInterface $builder,
        array $options
    ): void {
        $this->validate($options);
    }

private function validate(array $options): void
    {
        if (!isset($options['data'])) {
            throw new InvalidArgumentException(null, 'Missing data class.');
        }

        if (!$options['data'] instanceof $this->expectedClass) {
            throw new UnexpectedTypeException(
                $options['data'],
                $this->expectedClass
            );
        }
    }

$data is always empty dto when sent via ajax.
How to fix the situation?
PS But even if to hammer on the given listener. I have a second
$builder->get('stripeProductId')->addEventListener(
            FormEvents::POST_SUBMIT,
            function (FormEvent $event) use ($formModifier) {
                $stripeProductId = $event->getData();
                $formModifier($event->getForm()->getParent(), $stripeProductId);
            }
        );

Which id comes in, BUT I get Notice: Undefined index: stripeProductId on the output when filling out the form from dto
$forms['stripeProductId']->setData($data->stripeProductId);

mapper
public function mapDataToForms($viewData, $forms)
    {
        /** @var FormInterface[] $forms */
        $forms = iterator_to_array($forms);

        // initialize form field values
        $forms['stripeProductId']->setData($viewData->stripeProductId);
        $forms['stripePlanId']->setData($viewData->stripePlanId);
        $forms['stripeToken']->setData($viewData->stripeToken);
        $forms['stripeSource']->setData($viewData->stripeSource);
        $forms['stripeCoupon']->setData($viewData->stripeCoupon);
        $forms['stripeSourceName']->setData($viewData->stripeSourceName);
        if (isset($forms['freeTrial'])) {
            $forms['freeTrial']->setData($viewData->freeTrial);
        }
    }

    public function mapFormsToData($forms, &$viewData)
    {
        /** @var FormInterface[] $forms */
        $forms = iterator_to_array($forms);

        // as data is passed by reference, overriding it will change it in
        // the form object as well
        // beware of type inconsistency, see caution below
        $data = new PaymentCreateDTO(
            $forms['stripeProductId']->getData(),
            $forms['stripePlanId']->getData(),
            $forms['stripeToken']->getData(),
            $forms['stripeSource']->getData(),
            $forms['stripeCoupon']->getData(),
            $forms['stripeSourceName']->getData()
        );
    }

Answer the question

In order to leave comments, you need to log in

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question