Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Doctrine 2 Many to Many relation with additional columns in join table

So, I have been playing round with using doctrine for a while now and have it in some basic projects, but i decided to go back and have an in depth look into what it can do.

Ive now decided to switch to symfony 2 as my framework of choice and am looking into what doctrine 2 can do in more depth.

One thing i have been trying to get my head around is the many to many relationship within doctrine. I am starting to build a recipe system and am working on the relation between recipe and ingredients which gave me 3 entities, recipe, recipeIngredient and ingredient. The reason i cannot use a direct many to many relation is because i want to store two additional columns in the join table ( unit and quantity ) for each ingredient.

The problem i am having at the moment is that the entities persist ok, but the recipe_id in the join table is not inserted. I have tried everything i can think off and been through every thread and website looking for an answer . I am sure it is something completely obvious that i am missing. Please help, below is the code i have so far:

<?php
namespace Recipe\RecipeBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
 * @ORM\Entity
 * @ORM\Table(name="recipe")
 * @ORM\HasLifecycleCallbacks()
 */
class Recipe{

/**
 * @ORM\Id
 * @ORM\Column(type="integer")
 * @ORM\GeneratedValue(strategy="AUTO")
 */
protected $id;

/**
 * @ORM\OneToMany(targetEntity="RecipeIngredient", mappedBy="recipe", cascade=       {"persist"})
 */
protected $ingredients;
/**
 * @ORM\Column(type="string")
 * @var string $title
 *
 */
protected $title;
/**
 * Constructor
 */
public function __construct()
{
    $this->ingredients = new \Doctrine\Common\Collections\ArrayCollection();
}

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

/**
 * Add ingredients
 *
 * @param \Recipe\RecipeBundle\Entity\RecipeIngredient $ingredients
 * @return Recipe
 */
public function addIngredient(\Recipe\RecipeBundle\Entity\RecipeIngredient $ingredients)
{
    $ingredients->setRecipe($this);
    $this->ingredients[] = $ingredients;

    return $this;
}

/**
 * Remove ingredients
 *
 * @param \Recipe\RecipeBundle\Entity\RecipeIngredient $ingredients
 */
public function removeIngredient(\Recipe\RecipeBundle\Entity\RecipeIngredient $ingredients)
{
    $this->ingredients->removeElement($ingredients);
}

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

/**
 * Set title
 *
 * @param string $title
 * @return Recipe
 */
public function setTitle($title)
{
    $this->title = $title;

    return $this;
}

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

and recipeIngredient

/**
 * @ORM\Id
 * @ORM\Column(type="integer")
 * @ORM\GeneratedValue(strategy="AUTO")
 */
protected $id;

/**
 * @ORM\ManyToOne(targetEntity="Recipe", inversedBy="ingredients")
 * */
protected $recipe;

/**
 * @ORM\ManyToOne(targetEntity="Ingredient", inversedBy="ingredients" , cascade={"persist"})
 * */
protected $ingredient;

/**
 * @ORM\Column(type="string")
 * @var string  $quantity
 *
 */
protected $quantity;

/**
 * @ORM\Column(type="string")
 * @var string $unit
 *
 */
protected $unit;

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

/**
 * Set quantity
 *
 * @param string $quantity
 * @return RecipeIngredient
 */
public function setQuantity($quantity)
{
    $this->quantity = $quantity;

    return $this;
}

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

/**
 * Set unit
 *
 * @param string $unit
 * @return RecipeIngredient
 */
public function setUnit($unit)
{
    $this->unit = $unit;

    return $this;
}

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

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

    return $this;
}

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

/**
 * Set ingredient
 *
 * @param \Recipe\RecipeBundle\Entity\Ingredient $ingredient
 * @return RecipeIngredient
 */
public function setIngredient(\Recipe\RecipeBundle\Entity\Ingredient $ingredient = null)
{
    $this->ingredient = $ingredient;

    return $this;
}

/**
 * Get ingredient
 *
 * @return \Recipe\RecipeBundle\Entity\Ingredient
 */
public function getIngredient()
{
    return $this->ingredient;
}
}
like image 279
gristoi Avatar asked Nov 22 '12 09:11

gristoi


1 Answers

Your basic idea is the correct one. If you want to have a ManyToMany relation, but you need to add extra fields in the join table, the way to go is exactly as you have described: using a new entity having 2 ManyToOne relations and some additional fields.

Unfortunately you have not provided your controller code, because most likely your problem is there.

Basically if you do something like:

$ri = new RecipeIngredient;
$ri->setIngredient($i);
$ri->setRecipe($r);
$ri->setQuantity(1);
$em->persist($ri);
$em->flush();

You should always get a correct record in your database table having both recipe_id and ingredient_id filled out correctly.

Checking out your code the following should also work, although I personally think this is more sensitive to mistakes:

$ri = new RecipeIngredient;
$ri->setIngredient($i);
$ri->setQuantity(1);
// here we assume that Recipe->addIngredient also does the setRecipe() for us and 
// that the cascade field is set correctly to cascade the persist on $ri
$r->addIngredient($ri);
$em->flush();

For further reading I would suggest the other topics on this subject, such as: Doctrine2: Best way to handle many-to-many with extra columns in reference table

like image 173
Rein Baarsma Avatar answered Nov 14 '22 23:11

Rein Baarsma