6

I have built out a form which submits fine, however subform values all end up as null on the receiving end when I look at them in the controller.

Here is my UserProfileType form, based on the Userclass. So specifically, the subforms we are looking at are subscriptionTier1, subscriptionTier1, and subscriptionTier1:

class UserProfileType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('firstName', TextType::class)
            ->add('lastName', TextType::class)
            ->add('email', EmailType::class)

            // etc... I'll keep out the unimportant fields

            // here are the subforms whose values show up as null on the back end
            ->add('subscriptionTier1', UserSubscriptionTierType::class, [
                'required' => false,
                'mapped' => false
                ])
            ->add('subscriptionTier2', UserSubscriptionTierType::class, [
                    'required' => false,
                    'mapped' => false
                ])
            ->add('subscriptionTier3', UserSubscriptionTierType::class, [
                    'required' => false,
                    'mapped' => false
                ])
            ->add('save', SubmitType::class, [
                'attr' => ['class' => 'save'],
            ])
        ;

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
            'data_class' => User::class,
            'mode' => null
        )
    );
}

}

Here is what my UserSubscriptionTierType form type class looks like:

class UserSubscriptionTierType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class, [
                'attr' => [
                    'maxlength' => 25
                ]
            ])
            ->add('price', ChoiceType::class, [
                'choices' => [
                    '$10 per month' => 10,
                    '$20 per month' => 20,
                    '$30 per month' => 30
                ]
            ])
            ->add('description', TextareaType::class)
            ->add('messaging', CheckboxType::class, [
                'required' => false
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => UserSubscriptionTier::class,
        ));
    }
}

As you can see in the picture below, the subform subscriptionTier1's name, price, and description values are all set on the front-end:

enter image description here

Here is what the twig code looks like for the subscriptionTier1 subform:

    <div class="row justify-content-center w-100 ml-0 mr-0 mt-3 tier tier-1" >
        <div class="col-lg-6 mt-1">
            <div class="form-group form-control-lg w-100">
                <label for="firstname" class="mb-2">Tier 1 Name</label>
                {{ form_widget(form.subscriptionTier1.name, { 'attr': {'class': 'form-control'}}) }}
            </div>
        </div>
        <div class="col-lg-6 mt-1">
            <div class="form-group form-control-lg w-100">
                <label for="tier1price" class="mb-2">Tier 1 Price</label>
                {{ form_widget(form.subscriptionTier1.price, { 'attr': {'class': 'form-control'}}) }}
            </div>
        </div>
        <div class="col-lg-12 mt-3">
            <div class="form-group form-control-lg w-100">
                <label for="bio" class="mb-2">Tier 1 Description
                    <sup class="text-danger">*</sup>
                </label>
                {{ form_widget(form.subscriptionTier1.description, { 'attr': {'class': 'form-control border-box', 'rows':'8'}}) }}
            </div>
        </div>
        <div class="col-lg-12">
            <div class="form-group form-control-lg w-100">
                <div class="form-check">
                    <label class="form-check-label">
                        {{ form_widget(form.subscriptionTier1.messaging, { 'attr': {'class': 'form-control form-check-input'}}) }}
                        <span class="form-check-sign"></span>
                        Enable Messaging
                    </label>
                </div>
            </div>
        </div>
    </div>

Here is the code on the receiving end, the controller:

public function saveProfileAction(Request $request)
{
    $user = $this->getUser();

    $form = $this->createForm(UserProfileType::class, $user);
    $form->handleRequest($request);

dump($form->get('subscriptionTier1')->getData()); 

So in debugging, if I dump just the first form subscriptionTier1, you can see that the values are all null.

ProfileController.php on line 269:
Form {#2235 ▼
  -config: FormBuilder {#2236 ▶}
  -parent: Form {#2112 ▶}
  -children: OrderedHashMap {#2237 ▶}
  -errors: []
  -submitted: true
  -clickedButton: null
  -modelData: UserSubscriptionTier {#2280 ▼
    -id: null
    -posts: ArrayCollection {#2323 ▶}
    -subscriptions: null
    -name: null         // Don't understand why this is null 
    -price: null        // Don't understand why this is null 
    -description: null  // Don't understand why this is null 
    -tierNumber: null
    -versionNumber: null
    -messaging: false
    -user: null
    +"subsciptions": ArrayCollection {#2089 ▶}
  }
  -normData: UserSubscriptionTier {#2280 ▶}
  -viewData: UserSubscriptionTier {#2280 ▶}
  -extraData: []
  -transformationFailure: null
  -defaultDataSet: true
  -lockSetData: false
}

Would anyone know why the values are not passing to the back-end (or getting nulled out)?

Mike Doe
  • 16,349
  • 11
  • 65
  • 88
Brent Heigold
  • 1,213
  • 5
  • 24
  • 50
  • If it's not in `Request` object (you may also check raw `$_POST` data for sure), then you should check the rendered HTML. If data doesn't come with request, there's not much you can do on backend side. – Jakub Matczak Feb 11 '19 at 08:09

2 Answers2

1

Since the data_class option of UserProfileType form type is set to User class, the model data will be an instance of User class and since the User class doesn't have fields like subscriptionTier1 etc, these will not appear in your model data.

Instead, you could access unmapped fields in a form in a controller like this:

$subscriptionTier1 = $form->get('subscriptionTier1')->getData();

Documentation here

EDIT: You can only access the value of subscriptionTier1 etc, only after the form has been handled and only if the form has been submitted, otherwise it will be null:

public function saveProfileAction(Request $request)
{
    $user = $this->getUser();

    $form = $this->createForm(UserProfileType::class, $user);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $subscriptionTier1 = $form->get('subscriptionTier1')->getData();
        dump($subscriptionTier1);
    }
iiirxs
  • 4,493
  • 2
  • 20
  • 35
0

I don't know if this is just a workaround or of this is actually the best practices way to do it, but here's how I got it to work:

I had the following subform tags in: {{ form_start(form.subscriptionTier1) }} etc... {{ form_end(form.subscriptionTier1) }}

This will nest form tags. Apparently you are not allowed to nest forms like this.

So taking out the {{ form_start(form.subscriptionTier1) }} , {{ form_end(form.subscriptionTier1) }} for all the subscriptionTier1, subscriptionTier1, subscriptionTier1and then checking for the form values in the request object worked.

Brent Heigold
  • 1,213
  • 5
  • 24
  • 50