Example of a Doctrine 2.x many-to-many association class

by Niki on February 2, 2013

At some point while reading the doctrine 2.x documentation about association mapping there is a mention about association classes at the bottom of the section about unidirectional many-to-many associations.

Why are many-to-many associations less common? Because frequently you want to associate additional attributes with an association, in which case you introduce an association class. Consequently, the direct many-to-many association disappears and is replaced by one-to-many/many-to-one associations between the 3 participating classes.

http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#many-to-many-unidirectional

So while these associations are less common, no real example is given of an implementation of 2 entities and an association class.

In the next section I will try to give an example implementation of such a construct.

We will use the easy example of a recipe website that needs to associate users to recipes. So each user can be associated with multiple recipes and each recipe can be associated with multiple users. The catch is that the association between the user and the recipe must indicate if a user owns a recipe or if a user likes another user’s recipe.

Let’s start with the user Entity. I like using FOSUserBundle for my user implementations so that is what I will be using in this example.

User Entity

namespace NVC\UserBundle\Entity;

use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="User")
 */
class User extends BaseUser
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\OneToMany(targetEntity="UserRecipeAssociation", mappedBy="user")
     */
    protected $user_recipe_associations;

    public function __construct()
    {
        parent::__construct();

        $this->user_recipe_associations = new \Doctrine\Common\Collections\ArrayCollection();
    }

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

    /**
     * Add user_recipe_associations
     *
     * @param \NVC\UserBundle\Entity\UserRecipeAssociation $userRecipeAssociations
     * @return User
     */
    public function addUserRecipeAssociation(\NVC\UserBundle\Entity\UserRecipeAssociation $userRecipeAssociations)
    {
        $this->user_recipe_associations[] = $userRecipeAssociations;

        return $this;
    }

    /**
     * Remove user_recipe_associations
     *
     * @param \NVC\UserBundle\Entity\UserRecipeAssociation $userRecipeAssociations
     */
    public function removeUserRecipeAssociation(\NVC\UserBundle\Entity\UserRecipeAssociation $userRecipeAssociations)
    {
        $this->user_recipe_associations->removeElement($userRecipeAssociations);
    }

    /**
     * Get user_recipe_associations
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getUserRecipeAssociations()
    {
        return $this->user_recipe_associations;
    }
}

We have introduced a member with a target entity of the type UserRecipeAssociation. This type will be explained in the last section of this post

    /**
     * @ORM\OneToMany(targetEntity="UserRecipeAssociation", mappedBy="user")
     */
    protected $user_recipe_associations;

Now the Recipe entity. For the sake of clarity we keep it simple and implement just four fields: The unique id, a name, a description and an integer for the cooking time.

Recipe Entity

namespace NVC\RecipeBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Recipe
 *
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="NVC\RecipeBundle\Entity\RecipeRepository")
 */
class Recipe
{
    /**
     * @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;

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

    /**
     * @var integer
     *
     * @ORM\Column(name="cooking_time", type="integer")
     */
    private $cooking_time;

    /**
     * @ORM\OneToMany(targetEntity="NVC\UserBundle\Entity\UserRecipeAssociation", mappedBy="recipe")
     */
    protected $user_recipe_associations;

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

    /**
     * Set name
     *
     * @param string $name
     * @return Recipe
     */
    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 Recipe
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

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

    /**
     * Set cooking_time
     *
     * @param integer $cookingTime
     * @return Recipe
     */
    public function setCookingTime($cookingTime)
    {
        $this->cooking_time = $cookingTime;

        return $this;
    }

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

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->user_recipe_associations = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Add user_recipe_associations
     *
     * @param \NVC\UserBundle\Entity\UserRecipeAssociation $userRecipeAssociations
     * @return Recipe
     */
    public function addUserRecipeAssociation(\NVC\UserBundle\Entity\UserRecipeAssociation $userRecipeAssociations)
    {
        $this->user_recipe_associations[] = $userRecipeAssociations;

        return $this;
    }

    /**
     * Remove user_recipe_associations
     *
     * @param \NVC\UserBundle\Entity\UserRecipeAssociation $userRecipeAssociations
     */
    public function removeUserRecipeAssociation(\NVC\UserBundle\Entity\UserRecipeAssociation $userRecipeAssociations)
    {
        $this->user_recipe_associations->removeElement($userRecipeAssociations);
    }

    /**
     * Get user_recipe_associations
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getUserRecipeAssociations()
    {
        return $this->user_recipe_associations;
    }
}

Now what we want to do is introduce a third entity class that will hold the associations between the User entity and the Recipe entity.This entity will be called ‘UserRecipeAssociation’ and reside in the NVC\UserBundle\Entity namespace.

UserRecipeAssociation Entity

namespace NVC\UserBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * UserRecipeAssociation
 *
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="NVC\UserBundle\Entity\UserRecipeAssociationRepository")
 */
class UserRecipeAssociation
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
    /**
     **/

    /**
     *
     * @ORM\ManyToOne(targetEntity="User", inversedBy="user_recipe_associations")
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
     *
     */
    private $user;

    /**
     *
     * @ORM\ManyToOne(targetEntity="NVC\RecipeBundle\Entity\Recipe", inversedBy="user_recipe_associations")
     * @ORM\JoinColumn(name="recipe_id", referencedColumnName="id")
     */
    private $recipe;

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

    /**
     * Set user
     *
     * @param \NVC\UserBundle\Entity\User $user
     * @return UserRecipeAssociation
     */
    public function setUser(\NVC\UserBundle\Entity\User $user = null)
    {
        $this->user = $user;

        return $this;
    }

    /**
     * Get user
     *
     * @return \NVC\UserBundle\Entity\User
     */
    public function getUser()
    {
        return $this->user;
    }

    /**
     * Set recipe
     *
     * @param \NVC\RecipeBundle\Entity\Recipe $recipe
     * @return UserRecipeAssociation
     */
    public function setRecipe(\NVC\RecipeBundle\Entity\Recipe $recipe = null)
    {
        $this->recipe = $recipe;

        return $this;
    }

    /**
     * Get recipe
     *
     * @return \NVC\RecipeBundle\Entity\Recipe
     */
    public function getRecipe()
    {
        return $this->recipe;
    }
}

With these three code examples you should be able to produce your own association classes.

Previous post: