Prohibit duplication in a many-to-many relationship database

I am working on the back office of a restaurant website. When I add a dish, I can add the ingredients in two ways.

In my form template, I manually added a text input field. I have applied the jQuery UI autocomplete method in this field, which allows you to:

  • Select existing components (previously added)
  • Add ingredients

However, when I submit the form, each ingredient is inserted into the database (normal behavior that you tell me). For ingredients that do not exist, this is good, but I do not want to reinsert the ingredients that are already inserted.

Then I thought of Doctrine events like prePersist() . But I do not see how to proceed further. I would like to know if you have any idea how to do this.

Here is the buildForm method of my DishType :

 <?php public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('category', 'entity', array('class' => 'PrototypeAdminBundle:DishCategory', 'property' => 'name', 'multiple' => false )) ->add('title', 'text') ->add('description', 'textarea') ->add('price', 'text') ->add('ingredients', 'collection', array('type' => new IngredientType(), 'allow_add' => true, 'allow_delete' => true, )) ->add('image', new ImageType(), array( 'label' => false ) ); } 

and the method in my controller where I process the form:

 <?php public function addDishAction() { $dish = new Dish(); $form = $this->createForm(new DishType, $dish); $request = $this->get('request'); if ($request->getMethod() == 'POST') { $form->bind($request); if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($dish); $em->flush(); return $this->redirect($this->generateUrl('prototype_admin_get_dish', array('slug' => $dish->getSlug()))); } } return $this->render('PrototypeAdminBundle:Admin:addDish.html.twig', array( 'form' => $form->createView(), )); } 
+7
duplicates symfony many-to-many
source share
2 answers

I had the same problem. My objects were projects (dishes in your case) and tags (ingredients).

I solved this by adding an event listener as described here .

 services: my.doctrine.listener: class: Acme\AdminBundle\EventListener\UniqueIngredient tags: - { name: doctrine.event_listener, event: preUpdate } - { name: doctrine.event_listener, event: prePersist } 

The listener launches both prePersist (for new dishes added) and preUpdate for updates to existing dishes.

Code checks if an ingredient exists. If an ingredient exists, it is used and the new record is discarded.

The following code:

 <?php namespace Acme\AdminBundle\EventListener; use Doctrine\ORM\Event\LifecycleEventArgs; use Acme\AdminBundle\Entity\Dish; use Acme\AdminBundle\Entity\Ingredient; class UniqueIngredient { /** * This will be called on newly created entities */ public function prePersist(LifecycleEventArgs $args) { $entity = $args->getEntity(); // we're interested in Dishes only if ($entity instanceof Dish) { $entityManager = $args->getEntityManager(); $ingredients = $entity->getIngredients(); foreach($ingredients as $key => $ingredient){ // let check for existance of this ingredient $results = $entityManager->getRepository('Acme\AdminBundle\Entity\Ingredient')->findBy(array('name' => $ingredient->getName()), array('id' => 'ASC') ); // if ingredient exists use the existing ingredient if (count($results) > 0){ $ingredients[$key] = $results[0]; } } } } /** * Called on updates of existent entities * * New ingredients were already created and persisted (although not flushed) * so we decide now wether to add them to Dishes or delete the duplicated ones */ public function preUpdate(LifecycleEventArgs $args) { $entity = $args->getEntity(); // we're interested in Dishes only if ($entity instanceof Dish) { $entityManager = $args->getEntityManager(); $ingredients = $entity->getIngredients(); foreach($ingredients as $ingredient){ // let check for existance of this ingredient // find by name and sort by id keep the older ingredient first $results = $entityManager->getRepository('Acme\AdminBundle\Entity\Ingredient')->findBy(array('name' => $ingredient->getName()), array('id' => 'ASC') ); // if ingredient exists at least two rows will be returned // keep the first and discard the second if (count($results) > 1){ $knownIngredient = $results[0]; $entity->addIngredient($knownIngredient); // remove the duplicated ingredient $duplicatedIngredient = $results[1]; $entityManager->remove($duplicatedIngredient); }else{ // ingredient doesn't exist yet, add relation $entity->addIngredient($ingredient); } } } } } 

NOTE: this seems to work, but I'm not a symfony / doctrine expert, so check your code carefully

Hope this helps!

pcruz

+7
source share

Since this post is 2 years old, I don’t know if help is needed here, anyway.

You must put the addIngredient function in your Dish object, this function checks if the Ingredient object exists in the current ingredient collection.

 addIngredient(Ingredient $ingredient){ if (!$this->ingredients->contains($ingredient)) { $this->ingredients[] = $ingredient; } } 

Hope this helps you anyway.

0
source share

All Articles