Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony CollectionType & class inheritance

I'm struggling with a Symfony project, and since I'm not that experienced with the framework, I can't figure out if I have a design flaw, if Symfony can't handle my use case, or if I just need to find the right method.

Here it is :

I have an entity Row which should contain 1 to n items that have different content like "title", "text", "image", etc.

Since every content has different characteristics, I have extended each content type from an abstract class called RowContent via single table inheritance. Here is an edited version of the entities : Class Row

class Row
{
    //.....

    /**
     * @var ArrayCollection $rowContents
     *
     * @ORM\OneToMany(targetEntity="RowContent", mappedBy="row", cascade={"persist", "remove", "merge"})
     */
    private $rowContents;

    //...
}

Class RowContent:

/**
 * RowContent
 *
 * @ORM\Table(name="row_content")
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="discr", type="string")
 * @ORM\DiscriminatorMap({
 *     "title" = "Kinkinweb\BaseBundle\Entity\Content\Title",
 *     "text" = "Kinkinweb\BaseBundle\Entity\Content\Text",
 *     "image" = "Kinkinweb\BaseBundle\Entity\Content\Image",
 * })
 * @ORM\Entity(repositoryClass="Kinkinweb\BaseBundle\Repository\RowContentRepository")
 */

abstract class RowContent
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
    
    //...
}

and for example the Text Class :

/**
 * Text
 *
 * @ORM\Table(name="content_text")
 * @ORM\Entity(repositoryClass="Kinkinweb\BaseBundle\Repository\Content\TextRepository")
 */
class Text extends RowContent
{
    /**
     * @var string
     *
     * @ORM\Column(name="text", type="string", length=255)
     */
    private $text;

    //...
}

So far so good, but I can't handle form submission with all this... To handle forms with these entities, I have so far the following FormType :

class RowType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            //...
            ->add('rowContents', CollectionType::class, array(
                'entry_type'   => RowContentType::class,
                'allow_add'    => true,
                'allow_delete' => true,
                'label' => 'Contenu Flexible',
                'by_reference' => false,
                'block_name' => 'rows',
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Kinkinweb\BaseBundle\Entity\Row',
        ));
    }
}

I have dealt with the buildForm part like this :

    public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder->addEventListener(FormEvents::PRE_SET_DATA, function($e) {
                if (null === $e->getData()) { return; }
                $form = $e->getForm();
                if ($e->getData() instanceof Text){
                    $form->add('text',TextType::class);
                }
            });
        }
public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Kinkinweb\BaseBundle\Entity\RowContent';
        ));
    }

But when I add content in the form using jQuery and submit the form the framework can't handle the RowContent object submitted (seems logical to me since RowContent is abstract).

So before I go pulling out my hair one by one, I wonder if anyone already ran into a situation like this or has any insight on how to do my form submitting.

Thanks !

like image 723
Quentin Avatar asked Nov 08 '22 23:11

Quentin


1 Answers

I know this question is old, but I had the same problem and I managed to solve it. I'll share my solution in case someone stumble on the same issue in the future.

At the time of this post I'm using symfony 5.

  1. I created one form for each type. For instance, in your case it would be one form for Title, one form for Text and another for Image (let's call them RowTitleContentType, RowTextContentType and RowImageContentType respectively).

I did create an AbstractRowContentType and extended from it. This way I wouldn't have to repeat the common fields in every form type.

  1. Then, in your RowType form, you add a CollectionType for each form type you've created and set the 'mapped' property to false. In other words, it'd look like this:

     public function buildForm(FormBuilderInterface $builder, array $options)
     {
         $builder
             //...
             ->add('rowTitleContents', CollectionType::class, array(
                 'entry_type'   => RowTitleContentType::class,
                 'mapped' => false,
                 'allow_add'    => true,
                 'allow_delete' => true,
                 'by_reference' => false,
             ))
            ->add('rowTextContents', CollectionType::class, array(
                 'entry_type'   => RowTextContentType::class,
                 'mapped' => false,
                 'allow_add'    => true,
                 'allow_delete' => true,
                 'by_reference' => false,
             ))
            ->add('rowImageContents', CollectionType::class, array(
                 'entry_type'   => RowImageContentType::class,
                 'mapped' => false,
                 'allow_add'    => true,
                 'allow_delete' => true,
                 'by_reference' => false,
             ))
         ;
     }
    
  2. Now we need to set a listener in order to merge the contents and add them to a RowContent object:

     public function buildForm(FormBuilderInterface $builder, array $options)
     {
         //..
         $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
             $row = $event->getData();
             $form = $event->getForm();
    
             //let's get every content from the form
    
             $rowTitleContents = $form->get('rowTitleContents')->getData();
             $rowTextContents = $form->get('rowTextContents')->getData();
             $rowImageContents = $form->get('rowImageContents')->getData();
    
             //Let's merge everything
             $mergedRowContents = array_merge($rowTitleContents, 
             $rowTextContents, $rowImageContents);
    
             // And now we add everything to the object.
             $row->setRowContents(new ArrayCollection($mergedRowContents));
         });
     }
    

And that's it. Hope it can help someone.

By the way, my solution is based on this post: Build Form with Single Table Inheritance

like image 141
Rafael J. Mateo C. Avatar answered Jan 03 '23 01:01

Rafael J. Mateo C.