Profet
Profet

Reputation: 942

Doctrine cascade={remove} not working when the entity is deleted through onDelete="CASCADE"

I encountered a problem with cascading relationships between entities using Doctrine 2.

I have a Media entity, related to a parent event:

class Media
{

    /**
     * @ORM\OneToOne(targetEntity="Event", mappedBy="media")
     */
    private $event;

    public function getEvent()
    {
        return $this->event;
    }

    public function setEvent(Event $event)
    {
        $this->event = $event;
    }

}

Each Event is related to the media (bidirectional) and also related to an Import entity.

class Event
{

    /**
     * @ORM\JoinColumn(name="media", referencedColumnName="id", onDelete="SET NULL")
     * @ORM\OneToOne(targetEntity="Media", inversedBy="event", cascade={"persist", "remove"}, orphanRemoval=true)
     */
    private $media;

    public function getMedia()
    {
        return $this->media;
    }

    public function setMedia(Media $media = null)
    {
        $this->media = $media;
    }


    /**
     * @ORM\JoinColumn(name="import", referencedColumnName="id", nullable=true, onDelete="CASCADE")
     * @ORM\ManyToOne(targetEntity="Import")
     */
    private $import;

    public function getImport()
    {
        return $this->import;
    }

    public function setImport(Import $import = null)
    {
        $this->import = $import;
    }

}

The expected behavior is the following:

Both are working well:

However, if I delete an Import, although events are deleted, Medias related to deleted events are not.

Any ideas on what could be happening? Thanks!

Upvotes: 2

Views: 5728

Answers (1)

Alan T.
Alan T.

Reputation: 1430

The problem you are describing is the expected behaviour. The onDelete="CASCADE" option enforces a behaviour that is executed internally by your database while the option cascade={"remove"} is handled via Doctrine and applied to objects that are performed in memory as stated in the documentation:

Cascade operations are performed in memory. That means collections and related entities are fetched into memory (even if they are marked as lazy) when the cascade operation is about to be performed. This approach allows entity lifecycle events to be performed for each of these operations.

Both approaches are valid but they imply different things as discussed in this section.


What actually happens with your setup is that you expect a mix of onDelete="CASCADE" and cascade={"remove"} to operate together in your scenario which they can't due to their nature.

As it is, since you don't have an inverse side in Import with cascade={"remove"}, when you remove an Import:

  • a DELETE operation is performed in your database on the table corresponding to Import
  • Thanks to the foreign key in the table used for Event, events related to your Import are deleted directly in your database

From there, nothing else is performed since the table used for Media does not have any foreign key referring to the Event table (since it is on the inverse side of the association).


There are two things you can do to make this work:

Invert the Media/Event association and add onDelete="CASCADE" on Media table

in Media.php

/**
 * @ORM\OneToOne(targetEntity="Event", inversedBy="media")
 * @ORM\JoinColumn(onDelete="CASCADE")
 */
private $event;

in Event.php

/**
 * @ORM\OneToOne(targetEntity="Media", mappedBy="event")
 */
private $media;

With this approach, removing an Import entry in the database will necessarily remove related Event entries and through the same mechanism, related Media will be deleted (all this being directly done by your database, Doctrine just issued one DELETE operation on the Import table).

Add an inverse side in Import and use cascade={"remove"}

If you add an inverse OneToMany in Import with cascade={"remove"}, remove operations performed with the entity manager will be cascaded to related Event entities which will also cascade the remove operation to any associated Media.

This can be useful if you want lifecycle events to be performed for those entities.


This does not mean that you have to chose between both approaches. The documentation states the following:

You should be aware however that using strategy 1 (CASCADE=REMOVE) completely by-passes any foreign key onDelete=CASCADE option, because Doctrine will fetch and remove all associated entities explicitly nevertheless.

That being said, having onDelete=CASCADE in addition to cascade={"remove"} makes sense if you want your database to stay in a proper state. For example, if you execute DELETE queries directly (without using the entity manager), related entries would not be removed without onDelete=CASCADE and your RDBMS would most likely complain about invalid foreign key constraints.

Upvotes: 5

Related Questions