Draeli
Draeli

Reputation: 162

Symfony 3.x : form with 1-N-1

I am trying to create a form for a media with a sub collection of tags. Following doctrine documentation I create that :

Test\CommonBundle\Entity\Media:
    type: entity
    table: media
    repositoryClass: Test\CommonBundle\Repository\MediaRepository
    indexes:
        index_title:
            columns: [ 'title' ]
    fields:
        id:
            type: integer
            id: true
            generator: { strategy: IDENTITY }
            nullable: false
        title:
            type: string
            nullable: false
            length: 200
    oneToMany:
        tagAssociate:
            targetEntity: Test\CommonBundle\Entity\TagAssociateMedia
            mappedBy: targetAssociate

Test\CommonBundle\Entity\TagAssociateMedia:
    type: entity
    table: tag_associate_media
    repositoryClass: Test\CommonBundle\Repository\TagAssociateMediaRepository
    fields:
        createdAt:
            type: datetime
            nullable: true
    manyToOne:
        targetAssociate:
            targetEntity: Test\CommonBundle\Entity\Media
            inversedBy: tagAssociate
            joinColumn:
                name: target_id
                referencedColumnName: id
        tag:
            targetEntity: Test\CommonBundle\Entity\TagMedia
            inversedBy: tagAssociate
            joinColumn:
                name: tag_id
                referencedColumnName: id

Test\CommonBundle\Entity\TagMedia:
    type: entity
    table: tag_media
    repositoryClass: Test\CommonBundle\Repository\TagMediaRepository
    indexes:
        index_name:
            columns: [ 'name' ]
    fields:
        id:
            type: integer
            id: true
            generator: { strategy: IDENTITY }
            nullable: false
        name:
            type: string
            nullable: false
            length: 100
    oneToMany:
        tagAssociate:
            targetEntity: Test\CommonBundle\Entity\TagAssociateMedia
            mappedBy: tag

I create entities. For exemple I have for Media :

<?php
namespace Test\CommonBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;

use \Test\CommonBundle\Entity\TagAssociate;

/**
* Media
*/
class Media
{
    /**
     * @var integer
     */
    protected $id;

    /**
     * @var string
     */
    protected $title;

    /**
     * @var \Doctrine\Common\Collections\Collection
     */
    protected $tagAssociate;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->tagAssociate = new ArrayCollection();
    }

    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set title
     *
     * @param string $title
     *
     * @return Media
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    /**
     * Get title
     *
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }


    /**
     * Add tagAssociate
     *
     * @param \Test\CommonBundle\Entity\TagAssociate $tagAssociate
     *
     * @return Media
     */
    public function addTagAssociate(TagAssociate $tagAssociate)
    {
        $this->tagAssociate[] = $tagAssociate;

        $tagAssociate->setTargetAssociate($this);

        return $this;
    }

    /**
     * Remove tagAssociate
     *
     * @param \Test\CommonBundle\Entity\TagAssociate $tagAssociate
     */
    public function removeTagAssociate(TagAssociate $tagAssociate)
    {
        $this->tagAssociate->removeElement($tagAssociate);
    }

    /**
     * Get tagAssociate
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getTagAssociate()
    {
        return $this->tagAssociate;
    }
}

I create the TagMedia on the same model and create intermediate entity with $targetAssociate and $tag attributs and their getter/setter. Until now, I can manipulate my entities without problem.

Now the problem, I try to create a Form like this :

<?php
namespace Test\BackBundle\Entity\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;

use Symfony\Component\Form\FormInterface;

use Test\CommonBundle\Entity\Media;

class MediaType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options){
        $builder
            ->add('title', TextType::class, array(
                'label' => 'entity.media.title',
                'required' => true,
            ))
            ->add('tagAssociate', CollectionType::class, array(
                'label' => 'entity.tagAssociate',
                'required' => false,
                'entry_type' => TagAssociateType::class,
                'allow_add'    => true,
                'allow_delete' => true,
            ))
        ;

    }

    public function configureOptions(OptionsResolver $resolver){
        $resolver->setDefaults(array(
            'data_class' => Media::class,
            'translation_domain' => 'messages',
            'validation_groups' => function (FormInterface $form) {
                $data = $form->getData();
                $id = $data->getId();

                if( $id ){
                    return array('update');
                }

                return array('Default');
            },
        ));
    }

    public function getName(){
        return 'test_back_type_media';
    }
}

and the sub Form :

<?php
namespace Test\BackBundle\Entity\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

use Symfony\Component\Form\FormInterface;

use Test\CommonBundle\Entity\TagAssociateMedia;

class TagAssociateType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options){
        $builder
            // what i am suppose to add here ?
            ->add('tag', EntityType::class, array(
                'class' => TagMedia::class,
                'label' => 'entity.tag',
                'required' => false,
                'choice_label' => 'name',
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver){
        $resolver->setDefaults(array(
            'data_class' => TagAssociateMedia::class,
            'translation_domain' => 'messages',
            'validation_groups' => function (FormInterface $form) {
                $data = $form->getData();
                $id = $data->getId();

                if( $id ){
                    return array('update');
                }

                return array('Default');
            },
        ));
    }

    public function getName(){
        return 'test_back_type_tag_associate';
    }
}

With that, I don't have error and I have 0 and 1 display (in twig with {{ form_row(form.tagAssociate) }}) when I have 2 ligns in the relation as in this screen : enter image description here

But my problem come when I want to have this : enter image description here I want to have a list of checkbox corresponding to the relation and when I uncheck, on the update, this relation will be remove (I will manage in JS to add new relation with prototype and ajax request to ask server about available tags).

Any idea how I can do ?

Upvotes: 2

Views: 122

Answers (2)

Vincenzo Petrucci
Vincenzo Petrucci

Reputation: 865

I don't understand why you have TagAssociateMedia. This entity doesn't provide additional data, it's just a bridge between Media and TagMedia. If this is the case you can skip TagAssociateMedia and add TagMedia as a ManyToMany relation with Media. Doctrine will handle the associative table on its own.

This way is really easy to have the result you want, just add the field in your form:

->add('tagMedia', EntityType::class, array(
    'class' => 'TestCommonBundle:TagMedia',
    'multiple' => true,
    'expanded' => true,
    'required' => false
))

Upvotes: 0

Sma&#239;ne
Sma&#239;ne

Reputation: 1389

You have to call the query builder inside the form to call the tags entity https://symfony.com/doc/current/reference/forms/types/entity.html#using-a-custom-query-for-the-entities For remove you just have to unset the relation between the entities in the Media Class with a specific method that you create. Using Ajax you send the tag id, get the Entity in your logic and remove the relation

Upvotes: 1

Related Questions