Question

I am running Symfony 2.3 with Doctrine and these three (relevant) entities: Publication, Author and AuthorPublication. Both, Author and Publication have a Many-to-One relationship to AuthorPublication (so it is basically a Many-to-Many relation between Author and Publication but I need the AuthorPublication Entity to order the authors of a publication)

I want to have a form where a user can create a new publication and choose as many authors for that publication as he wants.

I studied this article: How to Embed a Collection of Forms but I do not understand how to apply that to my problem because of the AuthorPublication entity which lies in between.

Relevant Code:

Publication

<?php

namespace ind\PubBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="ind\PubBundle\Repository\PublicationRepository")
 * @ORM\Table("publications")
 */
class Publication {

    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $pid;

    /**
     * @ORM\OneToMany(targetEntity="AuthorPublication", mappedBy="publication")
     * @ORM\OrderBy({"order_id" = "ASC"})
     */
    protected $publicationAuthors;


//some more attributes + getters/seters
?>

Author

<?php

namespace ind\PubBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table("aid_author")
 */
class Author {

    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $aid;

    /**
     * @ORM\Column(type="string", length=255)
     */
    protected $author_surname;

    /**
     * @ORM\Column(type="string", length=255)
     */
    protected $author_forename;

    /**
     * @ORM\OneToMany(targetEntity="AuthorPublication", mappedBy="author")
     */
    protected $authorPublication;
?>

AuthorPublication

<?php

namespace ind\PubBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table("aid_pid")
 */
class AuthorPublication {

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     */
    protected $aid;

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     */
    protected $pid;

    /**
     * @ORM\Column(type="integer")
     */
    protected $order_id;

    /**
     * @ORM\ManyToOne(targetEntity="Publication", inversedBy="publicationAuthors")
     * @ORM\JoinColumn(name="pid", referencedColumnName="pid")
     */
    protected $publication;

    /**
     * @ORM\ManyToOne(targetEntity="Author", inversedBy="authorPublication")
     * @ORM\JoinColumn(name="aid", referencedColumnName="aid")
     */
    protected $author;
?>
Was it helpful?

Solution

  1. You have to make an AuthorPublicationType form. You put field author as an 'entity' and your others fields...

  2. You make your PublicationType including AuthorPublication (Embedded Forms).

  3. Then you can add new AuthorPublication with prototype and very simple javascript.

  4. Note that when you have to save your entity Publication with an authorPublication attribut null first. And after update Publication with authorPublication defined by using temporary ArrayCollection in Publication and LifecycleCallbacks for example.

EDIT : Example.

My entities are Sport OneToMany SportParam ManyToOne ConfigParam. SportParam is composed of a Sport, a ConfigParam and a value.

I want to create a new Sport with multiple ConfigParam :

  1. SportParamType :

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder        
            ->add('ConfigParam', 'entity', array(
                'class'    => 'PPHBSportScoringBundle:ConfigParam',
                'property' => 'nom',
                'multiple' => false,
                'label'=>'Paramètre'
            ))
            ->add('valeur','number',array('precision'=>2))
        ;
    }
    
  2. SportType

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('nom')
            ->add('SportParams',  'collection', array(
                'type'=> new SportParamType(),
                'allow_add'    => true,
                'allow_delete' => true,
                'by_reference'=> false
            ))
        ;
    
    }
    
  3. My form.html.twig :

    {{ form_start(form) }}
      {{ form_errors(form) }} 
      {{ form_row(form.nom) }}
      <ul class=SportParams data-prototype="{{ form_widget(form.SportParams.vars.prototype)|e }}">
        {% for param in form.SportParams %}
          <li>
          {{ form_errors(param.ConfigParam) }}
          {{ form_widget(param.ConfigParam) }}
          {{ form_errors(param.valeur) }}
          {{ form_widget(param.valeur) }}
          </li>
        {% endfor %}
      </ul>
      <input type="submit" class="btn btn-primary" />
    {{ form_end(form) }}
    

    My Javascript refine because it contains more code (AJAX call). It maybe contains some mistake. If it's not clear have a look to the documentation :

    <script type="text/javascript">
    
    var $container = $('ul.SportParams');
    // button to add a new SportParam
    var $addSportParamLink = $('<a href="#" id="ajout_param" class="btn btn-primary btn-xs">Ajouter un paramètre</a>');  
    var $newLinkLi = $('<li></li>').append($addSportParamLink);
    
    $(document).ready(function() {
        //delete button on each existing SportParam
        $container.find('li').each(function() {
            addParamFormDeleteLink($(this));
        });       
    
        //add button
        $container.append($newLinkLi);
    
        // adding a new form when cliking Add button
        $addSportParamLink.on('click',function(e) {         
            e.preventDefault(); // évite qu'un #apparaisse dans l'URL
            var index = $container.children().length-1;
    
            addParamForm($container,$newLinkLi);
    
            var bAffiche; 
    
            return false;
        });
    
        // adding a new form SportParam
        function addParamForm($container, $newLinkLi) {
    
            var $prototype = $container.attr('data-prototype');
    
            var newForm = $prototype.replace(/__name__/g, $container.children().length-1);  
    
            var $newFormLi = $('<li></li>').append(newForm);
            $newLinkLi.before($newFormLi);
    
            addParamFormDeleteLink($newFormLi);
        }
    
        function addParamFormDeleteLink($paramFormLi){
            var $removeFormA = $('<a href="#" class="btn btn-danger  btn-xs">Supprimer</a>');   
            $paramFormLi.append($removeFormA);
            $removeFormA.on('click', function(e) {
                e.preventDefault();
                $paramFormLi.remove();
            });
        }
    
    });
    </script>
    
  4. Sport Entity call's back :

    /**
     * sport
     *
     * @ORM\Table()
     * @ORM\Entity
     * @ORM\HasLifecycleCallbacks
     * @ORM\Entity(repositoryClass="SportRepository")
     */
    class Sport
    {
    
    ...Entity attribut...
    
        /**
         * @var ArrayCollection
         *
         * To save Sport without SportParams
         */
        private $SportParamsTMP;
    
    ...getter and setter ...
    
        /**
         * @ORM\PrePersist
         */
        public function saveSportParams()
        {   
          $this->SportParamsTMP = $this->SportParams;
          $this->SportParams = null;
        }
        /**
         * @ORM\PostPersist
        */
        public function restoreSportParams()
        {
          if ($this->SportParamsTMP == !null) {
            $this->SportParams = $this->SportParamsTMP;
          }
          $this->SportParamsTMP = null;
        }
    
    }   
    

    Finally the controller 's function to add a new Sport :

    public function addAction()
    {
      $sport = new Sport();
      $form = $this->createForm(new SportType(), $sport);
    
      $request = $this->getRequest();
    
      if ($request->getMethod() == "POST") {
        $form->bind($request);
        if($form->isValid()) {
          $em = $this->getDoctrine()->getManager();
    
          $em->persist($sport);
          //saving sport without parameter
          $em->flush();
    
          //updating sport with parameter
          $em->flush();
    
          return $this->redirect($this->generateUrl('pphb_sport_liste'));
        }
      }
    
      return $this->render('PPHBSportScoringBundle:Championnat/Sport:add.html.twig', array(
                 'form' => $form->createView(),
      ));
    }
    

I hope it help.
Don't know if it's the best way to do it, but it's working for me. If something to improve let me know please.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top