【问题标题】:Doctrine 2 Many to Many relation with additional columns in join tableDoctrine 2 多对多关系与连接表中的附加列
【发布时间】:2012-11-10 16:16:09
【问题描述】:

所以,我一直在玩转学说,并在一些基本项目中使用它,但我决定回去深入研究它可以做什么。

我现在决定切换到 symfony 2 作为我选择的框架,并且正在研究学说 2 可以更深入地做些什么。

我一直试图弄清楚的一件事是教义中的多对多关系。我开始构建一个食谱系统,并且正在研究食谱和成分之间的关​​系,这给了我 3 个实体,食谱、食谱成分和成分。我不能使用直接多对多关系的原因是因为我想在连接表中为每种成分存储两个额外的列(单位和数量)。

我现在遇到的问题是实体仍然存在,但是连接表中的 recipe_id 没有插入。我已经尝试了我能想到的一切,并通过每个线程和网站寻找答案。我敢肯定,我错过了一些完全显而易见的事情。请帮忙,下面是我到目前为止的代码:

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

和配方成分

/**
 * @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;
}
}

【问题讨论】:

  • 向我们展示你如何坚持RecipieIngredient...
  • 是的,这段代码似乎很好......正如格雷莫所说,请告诉我们更多细节
  • 最好的猜测是您没有明确地将 Recipe 实例设置到 RecipeIngredient 实例中,并且由于 RI 是拥有方,因此关系不会持久保存到数据库中。

标签: php symfony doctrine


【解决方案1】:

你的基本想法是正确的。如果你想要一个多对多关系,但你需要在连接表中添加额外的字段,那么要走的路正是你所描述的:使用一个具有 2 个多对一关系和一些额外字段的新实体。

很遗憾,您没有提供控制器代码,因为很可能您的问题就在那里。

基本上,如果您执行以下操作:

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

您应该始终在您的数据库表中获得正确的记录,并正确填写了 recipe_id 和 ingredients_id。

检查您的代码以下也应该可以工作,虽然我个人认为这对错误更敏感:

$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();

为了进一步阅读,我会建议有关此主题的其他主题,例如:Doctrine2: Best way to handle many-to-many with extra columns in reference table

【讨论】:

    【解决方案2】:

    如果我正确理解了这个模型,那么配方的构造及其关联的配方成分是并发的。如果 receipeIngredient->setRecipe() 被调用,在你坚持之前你可能没有 id 并且没有 id,默认 null 将放置在 recipeIngredient->recipe 字段中。这通常使用 cascade: "persist" 处理(在您的示例中,配方字段不存在,但您可以在控制器中显式处理它:

    /** 
      * Creates a new Recipe entity.
      *
      */
    public function createAction(Request $request)
    {
      $em = $this->getDoctrine()->getManager();
      $form = $this->createForm(new RecipeType());  
      $form->bind($request);
    
      if ($form->isValid()){
        $data = $form->getData();
        $recipeId = $data->getId();
        $recipeIngredients=$data->getIngredients();
        $recipe=$em->getRepository('reciperecipeBundle:Recipe')
                   ->findOneById($RecipeId);
        if (null === $Recipe)
          {$Recipe=new Recipe();}  
        foreach ($recipeIngredients->toArray() as $k => $i){
          $recipeIngredient=$em->getRepository('reciperecipeBundle:recipeIngredient')
                               ->findOneById($i->getId());
          if (null === $recipeIngredient)
            {$recipeIngrediente=new RecipeIngredient();}  
          $recipe->addIngredient($i);
    
          // Next line *might* be handled by cascade: "persist"        
          $em->persist($recipeIngredient);
        }  
        $em->persist($Recipe);
        $em->flush();
        return $this->redirect($this->generateUrl('Recipe', array()));
      }   
    
      return $this->render('reciperecipeBundle:Recipe:new.html.twig'
                          ,array('form'   => $form->createView()));  
    }
    

    【讨论】:

      【解决方案3】:

      我不确定这是否是一个解决方案,但它很容易尝试它,并且可能会有所帮助。 当我创建这种关系时,我会编写另一个注释,@ORM\JoinColumn,如下例所示:

      我们有一个实体 A、一个实体 B 和一个代表关系的类 AB,并添加了一些其他字段,就像你的情况一样。

      我的关系如下:

      use Doctrine\ORM\Mapping as ORM;
      
      /**
       *  
       *
       * @ORM\Table(name="a_rel_b")
       * @ORM\Entity
       */
      class AB
      {
      
          /**
           * @var integer
           *  @ORM\Id
           * @ORM\ManyToOne(targetEntity="A", inversedBy="b")
           * @ORM\JoinColumn(name="a_id", referencedColumnName="id")  
           **/
      
          private $a;
      
      
          /**
           * @var integer
           *  @ORM\Id
           * @ORM\ManyToOne(targetEntity="B", inversedBy="a")
           * @ORM\JoinColumn(name="b_id", referencedColumnName="id")
           **/
          private $b;
      // ...
      

      name表示关系表中的字段名称,而referencedColumnName是被引用实体表中id字段的名称(即b_id是a_rel_b 引用表 B 中的列 id )

      【讨论】:

        【解决方案4】:

        你不能,因为它不再是一种关系 [根据定义,它是两个原始实体集合的笛卡尔积的子集]。

        您需要一个中间实体,同时引用 RecipeIngredient - 称它为 RecipeElementRecipeEntry 左右,然后添加您想要的字段。

        您也可以将地图添加到您的Recipe,在其中保存您保存的每个Ingredient 的属性,如果没有重复则易于维护。

        如需进一步阅读,请have a look at this popular question

        【讨论】:

          猜你喜欢
          • 2011-05-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-08-22
          • 2022-08-18
          • 1970-01-01
          • 2014-02-14
          相关资源
          最近更新 更多