Search code examples
phpformssymfonyarraycollection

ArrayCollection: retrieve collection in a form


I made a web application with Symfony2, in which a User has an array correlation ManytoMany with the entity Mission. The User can upload the entity $product through a form, and one of the data passed by the form is the mission associated to the user.

There are more than only one mission for every user; so, when he uploads a $product object, he should also be able to select the mission he prefers.

To upload the file I use a form in a controller in the following way:

       $form = $this->createFormBuilder($product)
           ->add('mission', 'entity', array('required' => true, 'multiple' => false, 'class' => 'AcmeManagementBundle:Mission', 'query_builder' => function($repository) { return $repository->createQueryBuilder('c')->orderBy('c.id', 'ASC'); },))              
      //...
           ->add('save', 'submit')
           ->getForm(); 

It works, but not fine: indeed in this field I can select all the mission stored, and not only the ones associated with the user.

I tried then with:

       $form = $this->createFormBuilder($product)
           ->add('mission', 'collection', array('required' => true) )
      //...
           ->add('save', 'submit')
           ->getForm(); 

It works but shows only one mission, and doesn't allow the user to select the preferred mission.

I tried also with:

           ->add('mission', 'collection', array('required' => true) )

but it tells me:

Neither the property "missions" nor one of the methods "getMissions()", 
"isMissions()", "hasMissions()", "__get()" exist and have public access 
in class "Acme\GroundStationBundle\Entity\Product".

How I should change my controller??

My product entity is:

class Product
{
/**
 * @var \Doctrine\Common\Collections\ArrayCollection
 * 
 * @ORM\OneToMany(targetEntity="Acme\ManagementBundle\Entity\Mission", mappedBy="product")
 */
 protected $mission;

//...

 /**
  * Set mission
  *
  * @param string $mission
  * @return Product
  */
 public function setMission($mission)
 {
     $this->mission = $mission;

     return $this;
 }

 /**
  * Get mission
  *
  * @return string 
  */
 public function getMission()
 {
     return $this->mission;
 }
//...

UPDATE ---

I will post also my product and mission entity, as asked in the comments

This is my User Entity is:

 abstract class User extends BaseUser
 {

      /**
      * @var \Doctrine\Common\Collections\ArrayCollection
      * 
      * @ORM\ManyToMany(targetEntity="Acme\ManagementBundle\Entity\Mission", inversedBy="users", orphanRemoval=true)
      * @ORM\JoinTable(name="user_mission")
      */
     private $missions;    
     /**
      * Add missions
      *
      * @param \Acme\ManagementBundle\Entity\Mission $missions
      * @return User
      */
     public function addMission(\Acme\ManagementBundle\Entity\Mission $missions)
     {
         $this->missions[] = $missions;

         return $this;
     }
//...

And my Mission Entity:

<?php

namespace Acme\ManagementBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

/**
 * @ORM\Entity
 */
class Mission {
    /** 
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     * @var integer
     */
    protected $id;
        /** 
     * @ORM\Column(type="string", length=60)
     * @var String
     */
    protected $name;
/**
 * @var \Doctrine\Common\Collections\ArrayCollection
 * 
 * @ORM\ManyToOne(targetEntity="Acme\GroundStationBundle\Entity\Product", inversedBy="mission")
 * @ORM\JoinColumn(name="productId", referencedColumnName= "id")
 */ 
private $product;
    /** 
     * @ORM\Column(type="string", length=600)
     * @var String
     */
    protected $description;
    /**
     * @var \Doctrine\Common\Collections\ArrayCollection
     *
     * @ORM\ManyToMany(targetEntity="Acme\ManagementBundle\Entity\User", mappedBy="missions", cascade={"all"}, orphanRemoval=true)
     */
    private $users;

    public function __construct(){
        $this -> users = new ArrayCollection();
    }

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

    /**
     * Set name
     *
     * @param string $name
     * @return Mission
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

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

    /**
     * Set description
     *
     * @param string $description
     * @return Mission
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

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

    /**
     * Add users
     *
     * @param \Acme\ManagementBundle\Entity\User $users
     * @return Mission
     */
    public function addUser(\Acme\ManagementBundle\Entity\User $users)
    {
        $this->users[] = $users;

        return $this;
    }

    /**
     * Remove users
     *
     * @param \Acme\ManagementBundle\Entity\User $users
     */
    public function removeUser(\Acme\ManagementBundle\Entity\User $users)
    {
        $this->users->removeElement($users);
    }

    /**
     * Get users
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getUsers()
    {
        return $this->users;
    }
    public function __toString()
    {
        return $this->name;
    }
/**
 * Set product
 *
 * @param \Acme\GroundStationBundle\Entity\Product $product
 * @return Mission
 */
public function setProduct(\Acme\GroundStationBundle\Entity\Product $product = null)
{
    $this->product = $product;

    return $this;
}

/**
 * Get product
 *
 * @return \Acme\GroundStationBundle\Entity\Product 
 */
public function getProduct()
{
    return $this->product;
}
}

Solution

  • Please take a look at my changes to your code.

    When you define One(product)ToMany(missions) relation you have situation like this:

    1. Product has many missions and must have an ArrayCollection of missions which you can add, remove or get all.

    class Product
    {
    /**
     * @var \Doctrine\Common\Collections\ArrayCollection
     * 
     * @ORM\OneToMany(targetEntity="Acme\ManagementBundle\Entity\Mission", mappedBy="product")
     */
    // RENAME this attribute to plural. Product HAS MANY missions
    // Than naming convention is "human readable" addMission and removeMission from collection but getMissions
    protected $missions;
    //...
    
    //
    // remove those functions:
    public function setMission($mission)
    //...
    public function getMission()
    //...
    
    //
    // add those functions:
    public function __construct(){
            $this->missions = new ArrayCollection();
        }
    
    public function addMission( Acme\ManagementBundle\Entity\Mission $mission )
    {
        $this->missions[] = $mission;
    
        return $this;
    }
    
    public function removeMission( Acme\ManagementBundle\Entity\Mission $mission )
    {
        $this->missions->removeElement( $mission );
    
        return $this;
    }
    
    public function getMissions()
    {
        return $this->missions;
    }
    //...
    

    2. Many MissionS are owned by one product. Change only annotation

    class Mission {
    //... 
    // RENAME inversedBy to missions
    /**
     * @var \Doctrine\Common\Collections\ArrayCollection
     * 
     * @ORM\ManyToOne(targetEntity="Acme\GroundStationBundle\Entity\Product", inversedBy="missions")
     * @ORM\JoinColumn(name="productId", referencedColumnName= "id")
     */ 
    private $product;
    

    EDIT START

    3. Opposite - MANY products Belongs to one Mission If there is situation like you mentioned in comment, then your Annotations are wrong. Look at this fix:

    class Product
    {
    // ...
    /**
     * @var \Doctrine\Common\Collections\ArrayCollection
     * 
     * @ORM\ManyToOne(targetEntity="Acme\GroundStationBundle\Entity\Mission", inversedBy="products")
     * @ORM\JoinColumn(name="missionId", referencedColumnName= "id")
     */
    protected $mission;
    
    // ...
    
    // roll back this functions:
    public function setMission($mission)
    public function getMission()
    
    // remove those functions
    public function __construct(){
    public function addMission( Acme\ManagementBundle\Entity\Mission $mission )
    public function removeMission( Acme\ManagementBundle\Entity\Mission $mission )
    public function getMissions()
    //...
    
    
    
    class Mission {
    // ...
    /**
     * @var \Doctrine\Common\Collections\ArrayCollection
     * 
     * @ORM\OneToMany(targetEntity="Acme\ManagementBundle\Entity\Product", mappedBy="mission")
     */ 
    private $products;
    
    // ...
    
    //
    // remove those functions:
    public function setProduct($product)
    public function getProduct()
    //...
    
    //
    // add those functions:
    public function __construct(){
            $this->products = new ArrayCollection();
        }
    
    public function addProduct( Acme\ManagementBundle\Entity\Product $product )
    {
        $this->products[] = $product;
    
        return $this;
    }
    
    public function removeProduct( Acme\ManagementBundle\Entity\Product $product )
    {
        $this->products->removeElement( $product );
    
        return $this;
    }
    
    public function geProducts()
    {
        return $this->products;
    }
    //...
    

    EDIT END

    3. After that remember to:

    $ php app/console doctrine:generate:entities AcmeGroundStationBundle:Product
    $ php app/console doctrine:generate:entities AcmeGroundStationBundle:Mission
    $ php app/console doctrine:schema:update --force
    

    Good Luck!