I need some help understanding how cascade
works with Symfony2 entity associations, in particular the "remove" option. Because when I try to remove an entity, there is a database foreign key conflict and it will not delete from the database.
I have two test entities:
/**
* @ORM\Entity
* @ORM\Table(name="test")
*/
class Test extends Entity\Base {
/**
* This is a OneToOne Unidirectional association, just so that we can get the
* current published version easily, based on the publishedId.
* @ORM\OneToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\TestVersion")
* @ORM\JoinColumn(name="publishedId", referencedColumnName="id")
*/
protected $published;
/**
* @ORM\Column(type="integer", nullable=true)
*/
protected $publishedId = NULL;
/**
* This is the regular OneToMany Bi-Directional Association, for all the versions.
* @ORM\OneToMany(targetEntity="\Gutensite\CmsBundle\Entity\View\TestVersion", mappedBy="view", cascade={"persist", "remove"}, orphanRemoval=true)
*/
protected $versions;
}
/**
* @ORM\Entity
* @ORM\Table(name="test_version")
*/
class TestVersion extends Entity\Base {
/**
* @ORM\ManyToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\Test", inversedBy="versions")
* @ORM\JoinColumn(name="viewId", referencedColumnName="id")
*/
protected $view;
/**
* The primary view entity that this version belongs to.
* @ORM\Column(type="integer", nullable=true)
*/
protected $viewId;
}
*Note: I originally had a bidirectional OneToOne relationship that had cascade={"persist", "remove"} but realized that wasn't needed. Either way, the problem is exactly the same.
In a controller, I create a test entity like this:
$view = new Test();
$version = new TestVersion();
// associate the version with the view
$view->addVersion($version);
// mark this version as the published version (this is where the problem happens, though)
$view->setPublished($version);
$em->persist($view);
$em->flush();
And this creates one primary record for test and one associated version in test_version:
|---------------------|
| table: test |
|---------------------|
| id | publishedId |
| 100 | 500 |
|---------------------|
|---------------------|
| table: test_version |
|---------------------|
| id | viewId |
| 500 | 100 |
|---------------------|
*Note: the test_version.viewId is set in a manual line via my test.addVersion() method that calls $version->setView($this).
But when I go to remove the primary entity in test:
$view = $em->getRepository("GutensiteCmsBundle:View\Test")->find($request->query->get('id'));
$em->remove($view);
$em->flush();
It tries to delete the main test entity but fails because it is referenced by the test_version.viewId.
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`cms`.`test_version`, CONSTRAINT `FK_635D070BCEB972AA` FOREIGN KEY (`viewId`) REFERENCES `test` (`id`))
And if I manually try to delete the test_version record in the database, it also fails with a similar error:
ERROR 1451: 1451: Cannot delete or update a parent row: a foreign key constraint fails (`cms`.`test`, CONSTRAINT `FK_D87F7E0C628CCE00` FOREIGN KEY (`publishedId`) REFERENCES `test_version` (`id`)) SQL Statement: DELETE FROM `cms`.`test_version` WHERE `id`='17'
Soooooo..... what is the point of having cascade={"remove"} if it doesn't actually delete the related entity (or the parent entity for that matter)?
The only way I can delete these records is if I unset the $view->publishId which breaks the foreign key association overlap.
$view = $em->getRepository("GutensiteCmsBundle:View\Test")->find($request->query->get('id'));
$view->setPublishedId(NULL);
$em->persist($view);
$em->flush();
$em->remove($view);
$em->flush();
So apparently this is caused because of an overlap between the OneToOne and the OneToMany associations. I can live with this necessity of unsetting the publishedId before deleting a view. But I would love feedback from an expert to know if this seems problematic. Or a suggestion for a better solution for those associations. However, I have other records (like multiple routes) that point to the single view entity, so that is why the versions are out on their own entity (also to keep the bulk of the data in another table for optimization). So my model seems quite logical, e.g. I do want a quick easy way to reference the published version. And I thought that a uni-directional relationship would not cause problems. But it does...