3
votes

Doctrine's many-to-many logic is confusing me a bit. I have a pretty simple many-to-many relationship of recipes to categories. My base entity classes are equally simple.

The Recipe entity class...

class Recipe
{
    /**
     * @ORM\ManyToMany(targetEntity="Category", inversedBy="categories")
     * @ORM\JoinTable(name="recipe_category")
     **/
    private $categories;

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="title", type="string", length=255)
     */
    private $title;


    public function __construct() {
        $this->categories = new \Doctrine\Common\Collections\ArrayCollection();
    }
}

And the Category entity class...

class Category
{
    /**
     * @ORM\ManyToMany(targetEntity="Recipe")
     **/
    private $recipes;

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;


    public function __construct() {
        $this->recipes = new \Doctrine\Common\Collections\ArrayCollection();
    }
}

Seems pretty strait forward and matches Doctrine (and Symfony2's) documentation examples. The strange behavior comes when I try and generate the getters and setters for these classes via the Symfony console app.

The relationship setters/getters are incorrect. Take, for instance, the Category setter in the Recipe class that's generated...

/**
 * Add categories
 *
 * @param \Namespace\CookbookBundle\Entity\Category $categories
 * @return Recipe
 */
public function addCategorie(\Namespace\CookbookBundle\Entity\Category $categories)
{
    $this->categories[] = $categories;

    return $this;
}

It looks like the auto-generation of the method name is off. It should be "addCategory" and should be passed a "category."

While I can just correct this manually, if I re-run the entity generator, it will just add them again.

Am I doing this incorrectly or is this just a quirk of the entity generator? Can I specify an over-ride via annotation?

2
Well what looks weird is that it is neither addCategories or addCategory but addCategorie...cheesemacfly
Agreed. I thought I might have screwed up some of the annotation but nothing seems to affect it which leads me to believe it's more of a core issue than an annotation/configuration problem.CVEEP
I ran into the same issue myself. Just correct manually, then run doctrine:generate:entities whenever you want, it won't replace your existing methods, and the misspelled methods won't hurt anything.keyboardSmasher
On my project, i have a many to many relationship between msgCategory and superCategory entities, the generator gave me the methods: addMsgCategory and getMsgCategoriesfkoessler

2 Answers

0
votes

You're not doing anything wrong as that's how symfony generates them. I usually don't use the app/console to generate them as currently they're not doing a good job. One example is as you've mentioned the pluralization of words as you've mentioned. Another obvious one is the fact that it's using the [] notation which is pretty much treating an ArrayCollection object as a PHP array. You should never treat ArrayCollections as arrays.

This is how I have implemented it myself:

public function addCategory(Category $category)
{
    if (!$this->categories->contains($category)
        $this->categories->add($category);

    return $this;
}

Which doesn't add duplicates to the Array collection if it's already added. Same thing goes with remove:

public function removeCategory(Category $category)
{
    if ($this->categories->contains($category)
        $this->categories->remove($category);
}

What I've run into many times is let's say you have 4 categories and you add and remove them

$r = new Recipe();
$c1 = new Category();
$c2 = new Category();


$r->addCategory($c1);
$r->addCategory($c2);
// at this point $r->getCategories()->toArray()[0] contains $c1
// and $r->getCategories()->toArray()[1] contains $c2

$r->removeCategory($c1);
// now $r->getCategories()->toArray()[0] is empty and
// $r->getCategories()->toArray()[1] contains $c2 still
// so in order to get the first category you need to:

$r->getCategories()->first();
0
votes

You are not doing anything wrong. It is just that Doctrine automatically tries to singularize the names of method stubs whenever there is a plural name for a collection property. This is the function that Doctrine calls when you run the command doctrine:generate:entities:

$methodName = Inflector::singularize($methodName);

In your case, Doctrine tries to 'singularize' the word categories but fails to recognize the singular form correctly, so it just removes an 's' from the end returning categorie.

Also, as you see, Doctrine does not singularize the parameter passed to the method stubs, leaving $categories instead of being consistent and modifying it to $categorie.

If you want to avoid this, either you do not use plural words for collections, or use plural words and change the methods afterwards. As @keyboardSmasher comments to your post, doctrine won't overwrite methods you already have when using the command doctrine:generate:entities, and wrong methods won't hurt much if left there alone.


A final note: using ArrayCollections as arrays is perfectly fine, so this code is correct:

$this->categories[] = $category;

ArrayCollection object implements Collection, which in turn implements ArrayAccess. It is done this way precisely to be able to use ArrayCollections as Arrays.