symfonydoctrine-ormformcollection

Removing records from Form Collection in Symfony / Doctrine One->Many Relationship


Trying to remove entity record(s) from a form collection that is embedded inside another form. I have successfully added entity record(s) to this same form collection.

The form collection entity (quoteItemDeliverable) is in a 1 to Many relationship with the parent entity (quoteItem).

Using symfony 3.3 / Doctrine and have been working from the respective documentation https://symfony.com/doc/3.3/form/form_collections.html

Using the documentation I can remove the form elements, that being the item in the collection as it exists in the form (twig). The dump() in the controller code below will show the items are removed from the collection in the edit form. But when the $quoteItem is persisted the removed collection items in the form are not removed from the collection in the entity (records not removed).

The controller code we are using

    /**
 * Edit an existing ring quote entity.
 *
 * @Route("/{id}/edit", name="quote-ring_edit")
 * @Method({"GET", "POST"})
 */
public function editAction (Request $request, QuoteItem $quoteItem)
{
    $type = $quoteItem->getCategory();

    $form = $this->createForm('UniflyteBundle\Form\QuoteItemType', $quoteItem, ['allow_extra_fields' => true, 'attr' => ['quote-type' => 2, 'category-type' => $type]]);
    $form->add('QuoteRing', QuoteRingType::class, ['label' => false]);

    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
        dump($quoteItem);
        $em = $this->getDoctrine()->getManager();

        $em->persist($quoteItem);
        $em->flush();

        //return $this->redirectToRoute('quote-ring_edit', ['id' => $quoteItem->getId()]);
    }

    return $this->render("quoteitem/ring.html.twig", [
        'quoteItem' => $quoteItem,
        'form_new'  => false,
        'type'      => $type,
        'form'      => $form->createView()
    ]);
}

The form Type for the collection looks like

          ->add('quoteItemDeliverables', CollectionType::class, [
        'entry_type'    => QuoteItemDeliverableType::class,
        'entry_options' => array ('label' => false),
        'by_reference'  => false,
        'allow_add'     => true,
        'allow_delete'  => true,
        'prototype'     => true,
      ])

The quoteItem entity to form collection annotation.

/**
 * @ORM\OneToMany(
 *     targetEntity="UniflyteBundle\Entity\QuoteItemDeliverable",
 *     mappedBy="quoteItem",
 *     cascade={"persist","detach","remove"}
 * )
 */
private $quoteItemDeliverables;

The methods in the quoteItem class for adding and removing items from the collection

/**
 * Add quoteItemDeliverable
 *
 * @param \UniflyteBundle\Entity\QuoteItemDeliverable $quoteItemDeliverable
 *
 * @return QuoteItem
 */
public function addQuoteItemDeliverable (\UniflyteBundle\Entity\QuoteItemDeliverable $quoteItemDeliverable)
{
    $quoteItemDeliverable->setQuoteItem($this);
    $this->quoteItemDeliverables[] = $quoteItemDeliverable;

    return $this;
}

/**
 * Remove quoteItemDeliverable
 *
 * @param \UniflyteBundle\Entity\QuoteItemDeliverable $quoteItemDeliverable
 */
public function removeQuoteItemDeliverable (\UniflyteBundle\Entity\QuoteItemDeliverable $quoteItemDeliverable)
{
    $this->quoteItemDeliverables->removeElement($quoteItemDeliverable);
}

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

The documentation states that for a one-to-many we might have to explicitly remove the relationship (snipplet from documentation link provided above). I don't understand why.

foreach ($originalTags as $tag) {
        if (false === $task->getTags()->contains($tag)) {
            // remove the Task from the Tag
            $tag->getTasks()->removeElement($task);

            // if it was a many-to-one relationship, remove the relationship like this
            // $tag->setTask(null);

            $em->persist($tag);

            // if you wanted to delete the Tag entirely, you can also do that
            // $em->remove($tag);
        }

Does anyone have an idea as to what I am doing wrong with the controller that the dump is reflecting properly but the action with the persist removing the records is not happening?

Thank you in advance for your time and efforts assisting in this challenge.

Update - : I followed DonCallisto ' s advice and was successful in removing items from the collection at the entity level with the following in the controller

if ($form->isSubmitted() && $form->isValid()) {
        $em = $this->getDoctrine()->getManager();

        // NOTE: Remove the quote item deliverables - will need for additional work as well?
        foreach ($em->getRepository('UniflyteBundle:QuoteItemDeliverable')->findAll() as $quoteItemDeliverable) {
            if (false === $quoteItem->getQuoteItemDeliverables()->contains($quoteItemDeliverable)) {
                $em->remove($quoteItemDeliverable);
                $em->persist($quoteItem);
            }
        }
        $em->flush();

        return $this->redirectToRoute('blah', ['id' => $quoteItem->getId()]);
    }

Solution

  • That's because you're removing it from the inversed side of the relation. Doctrine can actually check only for changes in owning side, so it's like you're not removing it at all as owning side isn't changed.

    You need to remove the item programmatically trough EntityManager or set orphanRemoval to true in the annotation (but I won't recommend it!). I won't recommend to use orphanRemoval because, even if it saves you from writing more code, it makes impossible to re-assign removed element to other collections (just saying for keep you safe from future headaches)

    Moreover be sure to have by_reference => false in the from at collection field, otherwise add* and remove* methods won't be called.