VaN
VaN

Reputation: 2210

Integrity constraint violation, foreign key constraint fails

I have 3 entity : Invoice, InvoiceItemService, Asset.

First, I create an Invoice and an InvoiceItemService, and I link them together with a ManyToOne relation on InvoiceItemService side (so InvoiceItemService is the owner side).

Then, I can create an Asset, from the Invoice object. An asset is kind of a copy of an Invoice, with a negative total. When I create an Asset, I link the Invoice's InvoiceItemService to the Asset too, with a ManyToOne relation between InvoiceItemService and Asset.

When I delete an Invoice, everything works fine, Invoice, Asset and InvoiceItemService are deleted with a cascade={"remove"} option.

When I delete an Asset, I would like only the asset to be deleted. But I get this foreign key error.

Here are my entities :

class Invoice
{
    /**
     * @ORM\OneToMany(targetEntity="Evo\BackendBundle\Entity\Asset", mappedBy="invoice", cascade={"remove"})
     */
    protected $assets;

    /**
     * @ORM\OneToMany(targetEntity="Evo\BackendBundle\Entity\InvoiceItemService", mappedBy="invoice", cascade={"persist", "remove"})
     */
    protected $services;
}

class Asset
{
    /**
     * @ORM\ManyToOne(targetEntity="Evo\BackendBundle\Entity\Invoice", inversedBy="assets")
     * @ORM\JoinColumn(nullable=false)
     * @Exclude
     */
    protected $invoice;

    /**
     * @ORM\OneToMany(targetEntity="Evo\BackendBundle\Entity\InvoiceItemService", mappedBy="asset")
     */
    protected $services;
}

class InvoiceItemService extends InvoiceItem
{
    /**
     * @ORM\ManyToOne(targetEntity="Evo\BackendBundle\Entity\Invoice", inversedBy="services")
     * @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
     * @Type("Evo\BackendBundle\Entity\Invoice")
     * @Exclude
     */
    protected $invoice;

    /**
     * @ORM\ManyToOne(targetEntity="Evo\BackendBundle\Entity\Asset", inversedBy="services")
     * @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
     * @Type("Evo\BackendBundle\Entity\Asset")
     * @Exclude
     */
    protected $asset;
}

Upvotes: 0

Views: 1380

Answers (1)

Peter Bailey
Peter Bailey

Reputation: 105868

As Cerad mentioned in the comments above, you'll need to implement this behavior yourself.

Since you'll need access to the entity manager, you can't do this with a simple lifecycle callback on the Asset entity itself. Instead, you'll need to register a service to listen for the event and perform the action at that point.

Example implementation

app/config.yml

services:
    your.bundle.association.manager:
        class: Your\Bundle\Model\AssociationManager
        tags:
            - { name: doctrine.event_listener, event: preRemove }

And then, the service class itself (unstested - might have bugs)

namespace Your\Bundle\Model;

use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\EntityManager;
use Your\Bundle\Entity\Asset;

class AssociationManager
{
  public function preRemove(LifecycleEventArgs $args)
  {
    if ($entity instanceof Asset)
    {
      $this->removeAssetAssociations($asset, $args->getEntityManager());
      return;
    }
  }

  protected function removeAssetAssociations(Asset $asset, EntityManager $em)
  {
    foreach ($asset->getServices() as $invoiceItemService)
    {
      $invoiceItemService->asset = null;
      $em->persist($invoiceItemService);
    }
  }
}

Hope this helps.

Upvotes: 1

Related Questions