Reputation: 942
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:
Event
entity is automatically deleted when it's parent Import
is deleted (ManyToOne relation with onDelete="CASCADE"
).Event
entity also contains a reference (OneToOne relation) to a Media
entity, that must be deleted when the Event is deleted.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
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
:
DELETE
operation is performed in your database on the table corresponding to Import
Event
, events related to your Import
are deleted directly in your databaseFrom 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:
onDelete="CASCADE"
on Media
tablein 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).
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