18/02/2017 - DOCTRINE, SYMFONY
In this post we're going to see how cascade={"remove"}
and orphanRemoval=true
work in bidirectional one-to-one relationships. The reason why we're using bidirectional relationship is because it provides navigational access in both directions. Our example is football focused.
If you try to delete a record in an entity where you have cascade={"remove"}
or orphanRemoval=true
property set, it will try to delete records in other associated entities as well. Always be very careful when using cascade={"remove"}
and orphanRemoval=true
operations because you might end up (not knowingly) delete other records in other entities based on your ORM.
inversedBy
attribute of the OneToOne relationship.mappedBy
attribute of the OneToOne relationship.nullable=false
forces a composition relationship so if you want to create a League then you have to have a Country to go with it.class Country
{
/**
* @ORM\OneToOne(targetEntity="League", mappedBy="country")
*/
private $league;
}
class League
{
/**
* @ORM\OneToOne(targetEntity="Country", inversedBy="league")
* @ORM\JoinColumn(name="country_id", referencedColumnName="id", nullable=false)
*/
private $country;
}
`country` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
)
`league` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`country_id` int(11) NOT NULL,
`name` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_3EB4C318F92F3E70` (`country_id`),
CONSTRAINT `FK_3EB4C318F92F3E70` FOREIGN KEY (`country_id`) REFERENCES `country` (`id`)
)
mysql> SELECT
-> `country`.`id` AS CountryID,
-> `country`.`name` AS CountryName,
-> `league`.`id` AS LeagueID,
-> `league`.`name` AS LeagueName
-> FROM `country`
-> INNER JOIN `league` ON `country`.`id` = `league`.`country_id`;
+-----------+-------------+----------+----------------+
| CountryID | CountryName | LeagueID | LeagueName |
+-----------+-------------+----------+----------------+
| 1 | England | 1 | Premier League |
+-----------+-------------+----------+----------------+
1 rows in set (0.00 sec)
class Country
{
/**
* @ORM\OneToOne(targetEntity="League", mappedBy="country")
*/
private $league;
}
class League
{
/**
* @ORM\OneToOne(targetEntity="Country", inversedBy="league")
* @ORM\JoinColumn(name="country_id", referencedColumnName="id", nullable=false)
*/
private $country;
}
Deleting Country: Results in "Integrity constraint violation" error as League is Country dependent.
$this->entityManager->remove($entity);
$this->entityManager->flush();
doctrine.DEBUG: SELECT ... FROM country ... LEFT JOIN league ...
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM country WHERE id = ? [1] []
doctrine.DEBUG: "ROLLBACK" [] []
Deleting League: League gets deleted but Country remains intact.
$this->entityManager->remove($entity);
$this->entityManager->flush();
doctrine.DEBUG: SELECT ... FROM league ...
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM league WHERE id = ? [1] []
doctrine.DEBUG: "COMMIT" [] []
If you used orphanRemoval=true
instead, the result would be exactly the same.
class Country
{
/**
* @ORM\OneToOne(targetEntity="League", mappedBy="country", cascade={"remove"})
*/
private $league;
}
class League
{
/**
* @ORM\OneToOne(targetEntity="Country", inversedBy="league")
* @ORM\JoinColumn(name="country_id", referencedColumnName="id", nullable=false)
*/
private $country;
}
Deleting Country: Country gets deleted as well as League.
$this->entityManager->remove($entity);
$this->entityManager->flush();
doctrine.DEBUG: SELECT ... FROM country ... LEFT JOIN league ...
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM league WHERE id = ? [1] []
doctrine.DEBUG: DELETE FROM country WHERE id = ? [1] []
doctrine.DEBUG: "COMMIT" [] []
Deleting League: League gets deleted but Country remains intact.
$this->entityManager->remove($entity);
$this->entityManager->flush();
doctrine.DEBUG: SELECT ... FROM league ...
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM league WHERE id = ? [1] []
doctrine.DEBUG: "COMMIT" [] []
If you used orphanRemoval=true
instead, the result would be exactly the same.
class Country
{
/**
* @ORM\OneToOne(targetEntity="League", mappedBy="country")
*/
private $league;
}
class League
{
/**
* @ORM\OneToOne(targetEntity="Country", inversedBy="league", cascade={"remove"})
* @ORM\JoinColumn(name="country_id", referencedColumnName="id", nullable=false)
*/
private $country;
}
Deleting Country: Results in "Integrity constraint violation" error as League is Country dependent.
$this->entityManager->remove($entity);
$this->entityManager->flush();
doctrine.DEBUG: SELECT ... FROM country ... LEFT JOIN league ...
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM country WHERE id = ? [1] []
doctrine.DEBUG: "ROLLBACK" [] []
Deleting League: League gets deleted as well as Country.
$this->entityManager->remove($entity);
$this->entityManager->flush();
doctrine.DEBUG: SELECT ... FROM league ...
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM league WHERE id = ? [1] []
doctrine.DEBUG: DELETE FROM country WHERE id = ? [1] []
doctrine.DEBUG: "COMMIT" [] []
If you used orphanRemoval=true
instead, the result would be exactly the same.
class Country
{
/**
* @ORM\OneToOne(targetEntity="League", mappedBy="country", cascade={"remove"})
*/
private $league;
}
class League
{
/**
* @ORM\OneToOne(targetEntity="Country", inversedBy="league", cascade={"remove"})
* @ORM\JoinColumn(name="country_id", referencedColumnName="id", nullable=false)
*/
private $country;
}
Deleting Country: Country gets deleted as well as League.
$this->entityManager->remove($entity);
$this->entityManager->flush();
doctrine.DEBUG: SELECT ... FROM country ... LEFT JOIN league ...
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM league WHERE id = ? [1] []
doctrine.DEBUG: DELETE FROM country WHERE id = ? [1] []
doctrine.DEBUG: "COMMIT" [] []
Deleting League: League gets deleted as well as Country.
$this->entityManager->remove($entity);
$this->entityManager->flush();
doctrine.DEBUG: SELECT ... FROM league ...
doctrine.DEBUG: SELECT ... FROM country ... LEFT JOIN league ...
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM league WHERE id = ? [1] []
doctrine.DEBUG: DELETE FROM country WHERE id = ? [1] []
doctrine.DEBUG: "COMMIT" [] []
In this example, Country is independent so whatever happens to League, we should NOT automatically delete Country behind the scene. At same time, League is Country dependent so if Country gets deleted then we should automatically delete League behind the scene. Based on this note, the best option for us to go with is, Cascade 2 where we put cascade={"remove"}
or orphanRemoval=true
only on Country.