2

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;
}
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Gianni Alessandro
  • 860
  • 6
  • 11
  • 28
  • do you have the required property and getters in the code of the entity Product? – Adib Aroui Jan 30 '14 at 01:58
  • 1
    @whitelettersandblankspaces edited the question and put my product entity. Should I change the annotations? – Gianni Alessandro Jan 30 '14 at 02:04
  • 2
    mission or missions? To avoid such questions personnaly I use command line to generate my entities. – Adib Aroui Jan 30 '14 at 02:08
  • @whitelettersandblankspaces Well, actually the product must be saved only with one mission, that's the reason for the object and the functions. Now I changed the functions in getMissions... it works but doesn't allow the user to select the preferred mission. – Gianni Alessandro Jan 30 '14 at 02:11
  • 1
    Then you shouldn't use a collection – Jivan Jan 30 '14 at 02:12
  • 1
    You're looking for the `entity` field type and the `query_builder` option to fetch only those missons related to the current user. `collection` is not the right field type in your case. – Nicolai Fröhlich Jan 30 '14 at 02:12
  • @GianniAlessandro could you post your Mission and User entities as well to check your relationships? – Jivan Jan 30 '14 at 08:15

2 Answers2

3

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!

WebHQ
  • 711
  • 8
  • 21
  • 1
    With ->add('missions', 'collection' ) in the controller I still can't choose. :( Actually, every product belong to only one mission, but every mission has a lot of object. – Gianni Alessandro Jan 30 '14 at 15:37
  • Then I'll try to change it all and let you know. – Gianni Alessandro Jan 30 '14 at 15:43
  • Ok, I solved that all the entities are valid. But still don't have the possibility to choose. – Gianni Alessandro Jan 30 '14 at 16:28
  • Inside product controller you use `->add('mission', 'entity', array('required' => true) )` ? – WebHQ Jan 30 '14 at 16:41
  • No, I was using ->add('missions', 'collection' ) – Gianni Alessandro Jan 30 '14 at 16:45
  • [Use this](http://symfony.com/doc/current/reference/forms/types/entity.html#basic-usage) : `->add('mission', 'entity', array( 'class' => 'AcmeGroundStationBundle:Mission', 'property' => 'name', ))` – WebHQ Jan 30 '14 at 16:49
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/46448/discussion-between-gianni-alessandro-and-webhq) – Gianni Alessandro Jan 30 '14 at 17:01
  • I have analyzed your classes, even run them on my test bundle DafaultController.php Line 38 change to this : `->add('mission', 'entity', array( 'class' => 'AcmeGroundStationBundle:Mission', 'property' => 'name', 'multiple' => false, ))` You create Product owned by Mission, so there must be multiple => false. It works for me. I have made only one change. I removed Missions - Users ManyToMany relation, cuz i didn't had User class :) So if you will get other errors, please check Missions-Users part. – WebHQ Jan 31 '14 at 16:37
  • Please check a discussion. You have working bundle link there – WebHQ Jan 31 '14 at 16:57
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 ;)

Jivan
  • 21,522
  • 15
  • 80
  • 131
  • Thats the thing. @GianniAlessandro take a look at User->missions and Mission->users ORM annotations. You can see [examples in doctrine doc](http://docs.doctrine-project.org/en/latest/reference/association-mapping.html#one-to-many-bidirectional) for one-to-many annotations. – WebHQ Jan 30 '14 at 12:16
  • @Jivan I updated the annotations (you can see it also in the question) and updated the database, but still visualize: Neither the property "missions" nor one of the methods "getMissions()", "isMissions()", "hasMissions()", "__get()" exist and have public access in class "Acme\GroundStationBundle\Entity\Product". – Gianni Alessandro Jan 30 '14 at 12:50
  • @WebHQ I lost time for a stupid typo, but now annotations should be ok. – Gianni Alessandro Jan 30 '14 at 12:58
  • @Jivan Now I manage to update the database, but ->add('missions', 'collection') still gets error. – Gianni Alessandro Jan 30 '14 at 13:33
  • @GianniAlessandro Did you update your Doctrine schema with doctrine:generate:entities for both entities? Do you have a getMissions() method in your Product entity? Do you have a oneToMany relation in your Product entity and a manyToOne in your Mission entity? – Jivan Jan 30 '14 at 13:46
  • @GianniAlessandro could you post a link to a screenshot of the Symfony Profiler when you get the error? Click on the Symfony debug toolbar and open the Doctrine tab, and then take a screenshot of the "Mapping" area (in the bottom of the page) – Jivan Jan 30 '14 at 13:50
  • I did doctrine:generate:entities Acme, so it works for all the entities. The relations are shown above, the "Mapping" area shows 6 entities, but all of them are "Valid". In the product entity automatically appears the function getMission, not getMissions. If I change it, the Entity (in the Mapping area) is showed as not valid! – Gianni Alessandro Jan 30 '14 at 13:58
  • Try to manually change getMission for getMissions to fit your annotations (and the same for all mentions of "Mission" in your Product entity) - the manyToOne side of a relationship MUST reference the other side with a plural form - then set everything accordingly in your annotations/methods/properties and try ;) - you're not far – Jivan Jan 30 '14 at 14:03
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/46434/discussion-between-gianni-alessandro-and-jivan) – Gianni Alessandro Jan 30 '14 at 14:16
  • Hi again, You try to use a collection field type, whitch is [used to render a "collection" of some field or form. In the easiest sense, it could be an array of text fields that populate an array emails field](http://symfony.com/doc/current/reference/forms/types/collection.html) IMO you should change that type to [entity](http://symfony.com/doc/current/reference/forms/types/entity.html) witch id child type of select dedicated to use with objects. – WebHQ Jan 30 '14 at 14:23