Reputation: 699
I am trying to build a very basic CMS using Symfony
and Doctrine
. I have entities emulating my sites' structure like so:
Entity: Page
/**
* @ORM\Entity(repositoryClass="App\Repository\ContentTree\PageRepository")
*/
class Page extends SortableBase
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @ORM\OrderBy({"sort_order" = "ASC"})
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="Website", inversedBy="pages", cascade={"all"}, fetch="EAGER")
*/
private $website;
/**
* Owning side of relation
* @ORM\OneToMany(targetEntity="Section", mappedBy="page", fetch="EAGER")
*/
private $sections;
public function __construct()
{
...
Entity: Section
/**
* @ORM\Entity(repositoryClass="App\Repository\ContentTree\SectionRepository")
*/
class Section extends SortableBase
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="Page", inversedBy="sections", cascade={"all"}, fetch="EAGER")
*/
private $page;
/**
* Owning side of relation
* @ORM\OneToMany(targetEntity="Entry", mappedBy="section", fetch="EAGER")
*/
private $entries;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\SectionTemplating\SectionType", fetch="EAGER")
*/
private $sectionType;
public function __construct()
{
Entity: Entry
/**
* @ORM\Entity(repositoryClass="App\Repository\ContentTree\EntryRepository")
*/
class Entry extends SortableBase
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="Section", inversedBy="entries", cascade={"all"}, fetch="EAGER")
*/
private $section;
/**
* Owning side of relation
* @ORM\OneToMany(targetEntity="App\Entity\ContentTypes\Content", mappedBy="entry", fetch="EAGER")
*/
private $contents;
public function __construct()
{
...
So in the end a Page
can have Sections
which can have Entries
and so forth. Now, from what I gathered from the docs I was under the assumption that with how I setup the cascades
I could just go and use the EntityManager
to remove an instance of any Entity (lets say Section
) and it would automatically delete that instance as well as all contained Entries
.
However, when I do something like:
$section = $this->getSectionByID($sectionid);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->remove($section);
$entityManager->flush();
as soon as the Section
has any Entries
, I get:
An exception occurred while executing 'DELETE FROM section WHERE id = ?' with params [10]:
SQLSTATE[23000]: Integrity constraint violation:
1451 Cannot delete or update a parent row:
a foreign key constraint fails (`webdata`.`entry`, CONSTRAINT `FK_2B219D70D823E37A`
FOREIGN KEY (`section_id`) REFERENCES `section` (`id`))
I know what it it means but I just cannot figure out what I should do different here in order to force the EntityManager
to traverse down my entity graph and delete everything from bottom to top.
Upvotes: 1
Views: 923
Reputation: 699
Turns out that in order for Doctrine
to cascade a remove
down the entity-relation-graph one would simply annotate the owning side like so:
/**
* Owning side of relation
* @ORM\OneToMany(targetEntity="Page", mappedBy="website", cascade={"all"}, fetch="EAGER")
*/
private $pages;
This, however, really just tells the ORM what you want to happen during an e.g. remove
. This will not suffice to model what will happen during the actual call to your database. For this, one will have to add an additional @ORM\JoinColumn(onDelete="CASCADE")
to the inverse side, like so:
/**
* @ORM\ManyToOne(targetEntity="Website", inversedBy="pages", fetch="EAGER")
* @ORM\JoinColumn(onDelete="CASCADE")
*/
private $website;
With this everything works as intended. I am also finally able to manually remove entries via phpMyAdmin
which also gave me errors before.
Upvotes: 0
Reputation: 1311
A cascade is essentially a waterfall, in the sense that it determines where the operations will flow to next.
Cascade persist
means: when this entry gets persisted, let the operation also flow down to the child entities related to this one. The other operations work in a similar way.
In your case, the cascading seems to be initiated from the child entities. When you call:
$entityManager->remove($section);
The entity manager only notices cascading operations for page
, not for entries
.
When you put the cascade operation on Section
's $entries
instead, you might end up with a cascade delete that removes you page, for similar reasons.
Edit: In terms of annotations, that would make:
In Page:
/**
* @ORM\ManyToOne(targetEntity="Website", inversedBy="pages", fetch="EAGER")
*/
private $website;
/**
* Owning side of relation
* @ORM\OneToMany(targetEntity="Section", mappedBy="page", fetch="EAGER" , cascade={"all"})
*/
private $sections;
In Section:
/**
* @ORM\ManyToOne(targetEntity="Page", inversedBy="sections", fetch="EAGER")
*/
private $page;
/**
* Owning side of relation
* @ORM\OneToMany(targetEntity="Entry", mappedBy="section", fetch="EAGER", cascade={"all"})
*/
private $entries;
In Entry:
/**
* @ORM\ManyToOne(targetEntity="Section", inversedBy="entries", fetch="EAGER")
*/
private $section;
Are you sure you want to always eagerly fetch everything, btw?
Upvotes: 1