简体   繁体   中英

Symfony2 + Doctrine2: Entity Removal with Collections

Background Info

I have an entity called AnnualReport with multiple collections (let's say 2 for brevity's sake). Removal of one of these collections is handled automatically in the FormType:

        //AnnualReportStaffing entity collection
        ->add('staffingTenured', 'collection', array(
            'type' => new AnnualReportStaffingType(),
            'allow_add' => true,
            'allow_delete' => true,
            'by_reference' => false,
        ))

The other collection is a collection of files and deletion is NOT handled automatically :

        //AnnualReportDetail entity collection
        ->add('documents', 'collection', array(
            'type' => new AnnualReportDocumentType(),
            'allow_add' => true,
            'allow_delete' => false, // Do NOT automatically remove documents not in the collection (i.e. edit form where Documents are not passed again)
            'by_reference' => false,
        ))

This is the property/method declaration of each collection within my AnnualReport entity class:

/**
 * @ORM\ManyToMany(targetEntity="AnnualReportStaffing", cascade={"persist", "detach", "remove"}, orphanRemoval=true, fetch="LAZY")
 * @ORM\JoinTable(name="annualreports_staffingtenure",
 *      joinColumns={@ORM\JoinColumn(name="annualreport_id", referencedColumnName="id")},
 *      inverseJoinColumns={@ORM\JoinColumn(name="staffing_id", referencedColumnName="id", onDelete="CASCADE")},
 *      )
 */
private $staffingTenured;

/**
 * @ORM\ManyToMany(targetEntity="Document", cascade={"persist", "detach", "remove"}, orphanRemoval=true, fetch="LAZY")
 * @ORM\JoinTable(name="annualreports_documents",
 *      joinColumns={@ORM\JoinColumn(name="annualreport_id", referencedColumnName="id")},
 *      inverseJoinColumns={@ORM\JoinColumn(name="document_id", referencedColumnName="id", onDelete="CASCADE")},
 *      )
 */
private $documents;

public function __construct(AnnualReportUnit $unit, $year) {
    $this->staffingTenured = new ArrayCollection();
    $this->documents = new ArrayCollection();
}

/**
 * Add staffingTenured
 *
 * @param AppBundle\Entity\AnnualReportStaffing  $staffing
 * @return AnnualReport
 */
public function addStaffingTenured(AnnualReportStaffing $staffing)
{
    $this->staffingTenured->add($staffing);

    return $this;
}

/**
 * Remove staffingTenured
 *
 * @param AppBundle\Entity\AnnualReportStaffing  $staffing
 * @return AnnualReport
 */
public function removeStaffingTenured(AnnualReportStaffing $staffing)
{
    $this->staffingTenured->removeElement($staffing);

    return $this;
}

/**
 * Get staffingTenured
 *
 * @return ArrayCollection 
 */
public function getStaffingTenured()
{
    return $this->staffingTenured;
}

/**
 * Add document
 *
 * @param AppBundle\Entity\AnnualReportDocument  $document
 * @return AnnualReport
 */
public function addDocument(AnnualReportDocument $document)
{
    $this->documents->add($document);

    return $this;
}

/**
 * Remove document
 *
 * @param AppBundle\Entity\AnnualReportDocument  $document
 * @return AnnualReport
 */
public function removeDocument(AnnualReportDocument $document)
{
    $this->documents->removeElement($document);

    return $this;
}

/**
 * Get documents
 *
 * @return ArrayCollection
 */
public function getDocuments()
{
    return $this->documents;
}

Problem

When it comes time to delete an AnnualReport entity:

  • If documents are present, I am able to delete the documents collection, but I get a Foreign key constraint error about the staffingTenured items.
 An exception occurred while executing 'DELETE FROM annual_report WHERE id = ?' with params [57]: SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails 

( libcommand . annualreports_staffingtenure , CONSTRAINT FK_DB56517AD4F67A27 FOREIGN KEY ( annualreport_id ) REFERENCES annual_report ( id ))

  • If NO documents are present, the AnnualReport entity and all staffingTenured items are removed as expected.

This is the deleteAction():

public function deleteAction(Request $request, $id)
{
    $requestData = $request->request->all();
    $unit = $requestData['form']['unit'];


    $form = $this->createDeleteForm($id);
    $form->handleRequest($request);

    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();
        $entity = $em->getRepository('AppBundle:AnnualReport')->find($id);

        if (!$entity) {
            throw $this->createNotFoundException('Unable to find AnnualReport entity.');
        }

        //Remove any documents (allow_delete set to FALSE in form so have to do manually)
        $documents = $entity->getDocuments();
        foreach($documents as $document){
            $entity->removeDocument($document);
            //$em->remove($document);
        }
        $em->persist($entity);
        $em->flush();

        $em->remove($entity);
        $em->flush(); //flush again to remove the annual report
    }

    return $this->redirect($this->generateUrl('annualreportunit_edit', array('id' => $unit)));
}

You are mixing forms and ORM here. allow_delete is a form's parameter which means it's used to handle forms.

As Symfony's doc say about allow_delete :

If set to true, then if an existing item is not contained in the submitted data, it will be correctly absent from the final array of items. This means that you can implement a "delete" button via JavaScript which simply removes a form element from the DOM. When the user submits the form, its absence from the submitted data will mean that it's removed from the final array.

So it can be used to implement possibility of removing collection items from the root entity in the form . But it doesn't mean that Doctrine will magically handle it too.

If you want child entities to be saved/deleted when parent entity is, you should use cascade attribute in entity mapping .

How about you catch a ForeignKeyConstraintViolationException exception or better yet catch DBALException. Then show the user that the error. Or when you catch the exception you can remove child nodes and then remove the entity again.

use Doctrine\\DBAL\\Exception\\ForeignKeyConstraintViolationException;
Or
use Doctrine\\DBAL\\DBALException;

try {
    $em->remove($entity);
    $em->flush();

    $this->addFlash('success', 'Removed');
} catch (DBALException $e) {
    $em->remove($childEntity);
    $em->flush();

    $em->remove($entity);
    $em->flush();
--OR--
} catch (ForeignKeyConstraintViolationException $e) {
    $em->remove($childEntity);
    $em->flush();

    $em->remove($entity);
    $em->flush();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM