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 !
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.
I did create an AbstractRowContentType and extended from it. This way I wouldn't have to repeat the common fields in every form type.
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,
))
;
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With