Search code examples
formse-commercesymfony4many-to-one

Passing Entity object value in to nested CollectionTypes in a Symfony4 form


Overview: Foreach VariantGroup in a Products VariantSets I need to create a dropdown containg that VariantGroups Variants in the Symfony form

Details: I have a Product entity which is associated as a ManyToMany relationship to multiple VariantSet objects. The VariantSetobjects contain multiple VariantGroup objects which need to generate a dropdown each to display their Variant choices.

For the nested CollectionTypes I need to only make accessible the options related to the parent CollectionType.

So the only Variants available should be related to the VariantGroups which are only the ones related to the available VariantSets which are associated to the initially parsed Product.

There is some info out there that points toward using the query builder to grab the relevant items only but I'm wondering if this is best practice. Also - how do I pass the previous forms (so getting the top level Product in the nested CollectionType for VariantGroup as VariantSets sits between these two).

Is this even possible to achieve using best practices in Symfony forms?

Example of the desired output here


Solution

  • Yep. The answer was nested CollectionType forms and a custom query builder in the final form (for some reason it was calling all of the Variant objects in the DB, rather than using the ones associated to the parsed VariantGroup object:

    Main product form

    class ComplexProductType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
    
            // Render the variant sets based on the parsed product
            $builder
                ->add('variantSets', CollectionType::class, [
                    'entry_type' => VariantSetComplexProductType::class,
                    'label'=> false,
                    'by_reference' => true,
    
    
                ] );
            ;
        }
    
    
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults(array(
                "data_class" => Product::class,
            ));
        }
    
    }
    

    Render the products Variant Sets: (to get the correct VariantGroups associated to the main products VarianSet objects)

    class VariantSetComplexProductType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
    
            // Render the groups based on the Variant sets
            $builder
                ->add( 'label' )
                ->add( 'variantGroups', CollectionType::class, [
                    'entry_type' => VariantGroupComplexProductType::class,
                    'label'=> false,
                    'by_reference' => true
                ] )
            ;
        }
    
    
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults( [
                'data_class' => VariantSet::class,
            ] )
            ;
        }
    }
    

    Render the Variant Sets VariantGroups with its Variants in a dropdown:

    The drop down of Variant objects needs to be done in a FromEvent using the query_builder options, as otherwise I had an issue where all the Variant objects were being called in the DB.

    There need to be a check to make sure only the correct Variant objects were called based on the parsed VariantGroup.

    class VariantGroupComplexProductType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
    
            // Render the drop downs based on the Parsed variant group
            $builder
                ->add( 'label' )
                // Rendering of the drop down must be done after the previous form data is available
                ->addEventListener(FormEvents::PRE_SET_DATA, [$this, 'preSetData']);
            ;
        }
    
    
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults( [
                'data_class' => VariantGroup::class,
            ] );
        }
    
        public function preSetData(FormEvent $event)
        {
            $form = $event->getForm();
    
            /** @var VariantGroup $child */
            $child = $event->getData();
    
    
            $form
                ->add('variants', EntityType::class, array(
                    'class' => Variant::class,
                    'by_reference' => true,
                    'query_builder' => function (EntityRepository $er) use ( $child ) {
                        return $er->createQueryBuilder('u')
                            ->where('u.variant_group = :variant_group')
                            ->setParameter('variant_group', $child )
                            ->orderBy('u.label', 'DESC');
                    },
    'choice_label' => 'label',
                ));
    
        }
    }