ArrayCollection: retrieve collection in a form

2019-08-03 11:14发布

问题:

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;
}
}

回答1:

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!



回答2:

By saying this:

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

Symfony2 is telling you that you have to set a relationship between Mission and Product entities.

Try to create a oneToMany/manyToOne relationship between those entities by setting up annotations and properties in your objects. I'm not proficient enough in annotations, but I can tell what it would look like in Yaml:

# in Product:
oneToMany:
    missions:
        targetEntity: Mission
        mappedBy: product

# in Mission:
manyToOne:
    product:
        targetEntity: Product
        inversedBy: missions
        joinColumn:
            name: productId
            referencedColumnName: id

Before you test, don't forget to update your objects to your annotations:

$ php app/console doctrine:generate:entities YourBundle:Product
$ php app/console doctrine:generate:entities YourBundle:Mission

Then, tell us what happens in the commentaries. You're gonna have to do some testing before you get it working, in my opinion, but you're on the way ;)