0

This must be really basic stuff since I did not find any discussion about it. However I have struggled with this for a while.

I have pretty basic many-to-many relation with extra field implemented like in this example (double one-to-many relation). This works nicely when creating new entities and saving them to database. I am now trying to create editing feature and came across some problems.

Lets say my main entity is called Recipe, which has many-to-many relation with Ingredient entity. Extra field(s) like "amount" are in RecipeIngredient entity. Recipe class has setRecipeIngredient method, which adds RecipeIngredient object to ingredients array.

Should I create some "clearRecipeIngredients" method to Recipe class, which would remove all RecipeIngredient objects? I would call this when editing Recipe, then creating new RecipeIngredient entities from my data and populating ingredients array like when creating new entity? I admit that my cascade settings are probably not set correctly this to work, but I try fixing it next.

Any related examples would be great.

Community
  • 1
  • 1
jaylinen
  • 57
  • 7

1 Answers1

1

Strictly speaking, as you mentioned, there is no many-to-many relationship here, but a one-to-many followed by a many-to-one.

About your question, I wouldn't perform a bulk "clear" each time I want to edit the recipe. Instead, I'd provide a fluent interface to mimic the steps that would be taken if you wanted to edit a paper-based recipe.

I provided a sample implementation below:

class Recipe
{
  /**
   * @OneToMany(targetEntity="RecipeIngredient", mappedBy="recipe")
   */
  protected $recipeIngredients;

  public function addIngredient(Ingredient $ingredient, $quantity)
  {
    // check if the ingredient already exists
    // if it does, we'll just update the quantity
    $recipeIngredient = $this->findRecipeIngredient($ingredient);
    if ($recipeIngredient) {
      $quantity += $recipeIngredient->getQuantity();
      $recipeIngredient->updateQuantity($quantity);
    }
    else {
      $recipeIngredient = new RecipeIngredient($this, $ingredient, $quantity);
      $this->recipeIngredients[] = $recipeIngredient;
    }
  }

  public function removeIngredient(Ingredient $ingredient)
  {
    $recipeIngredient = $this->findRecipeIngredient($ingredient);
    if ($recipeIngredient) {
      $this->recipeIngredients->removeElement($recipeIngredient);
    }
  }

  public function updateIngredientQuantity(Ingredient $ingredient, $quantity)
  {
    $recipeIngredient = $this->findRecipeIngredient($ingredient);
    if ($recipeIngredient) {
      $recipeIngredient->updateQuantity($quantity);
    }
  }

  protected function findRecipeIngredient(Ingredient $ingredient)
  {
    foreach ($this->recipeIngredients as $recipeIngredient) {
      if ($recipeIngredient->getIngredient() === $ingredient) {
        return $recipeIngredient;
      }
    }
    return null;
  }
}

Note: you'll need to setup cascade persist and orphan removal for this code to work properly.

Of course, if you take this approach, your UI shouldn't display a full form with all ingredients and quantities editable at once. Instead, all the ingredients should be listed, with a "delete" button on each line, as well as a "change quantity" button which would pop-up a (single-field) form to update the quantity, for example.

BenMorel
  • 34,448
  • 50
  • 182
  • 322