Bu yazıda cascade={"remove"} ve orphanRemoval=true doctrine özelliklerinin bidirectional one-to-one ilişkilerde nasıl kullanılacağını öğreneceğiz. Bidirectional ilişki kullanmamızın nedeni, her iki entity üzerinden her iki tarafa doğru gidebilme olanağımızın olmasıdır. Örneğimizin teması futbol olacak.


Ön bilgi


Eğer cascade={"remove"} veya orphanRemoval=true özelliğinin ayarlı olduğu entityden bir kayıt silerseniz, bu işlem diğer ilişkisel entitylerden de otomatik olarak kayıtları silmeyi deneyecektir. Hem cascade={"remove"} hem de orphanRemoval=true özelliklerini entity ilişkilerinde ayarlarken çok dikkatli olmanız gerekir çünkü, yanlışlıkla arka planda bilginiz dışında diğer entity kayıtlarınıda silebilirsiniz.


Dizayn




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)

Testler


Cascade 1) Cascade yok


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 silme: League'in varlığı Country'nin varlığına bağlı olduğu için "Integrity constraint violation" hatası oluşur.


$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" [] []

League silme: League silinir ama Country olduğu gibi kalır.


$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" [] []

Cascade 2) Cascade Country


Eğer cascade={"remove"} yerine orphanRemoval=true kullanmış olsaydınız, sonuç değişmezdi.


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;
}

Country silme: Country ve League silinir.


$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" [] []

League silme: League silinir ama Country olduğu gibi kalır.


$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" [] []

Cascade 3) Cascade League


Eğer cascade={"remove"} yerine orphanRemoval=true kullanmış olsaydınız, sonuç değişmezdi.


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;
}

Country silme: League'in varlığı Country'nin varlığına bağlı olduğu için "Integrity constraint violation" hatası oluşur.


$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" [] []

League silme: League ve Country silinir.


$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" [] []

Cascade 4) Cascade Country ve League


Eğer cascade={"remove"} yerine orphanRemoval=true kullanmış olsaydınız, sonuç değişmezdi.


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;
}

Country silme: Country ve League silinir.


$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" [] []

League silme: League ve Country silinir.


$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" [] []

Sonuç


Bu örnekte Country bağımsız bir entitydir yani League'ye ne olursa olsun, arka planda Country'nin otomatik olarak silinmemesi gerekir. Bununla birlikte League'in varlığı Country'nin varlığına bağlı olduğu için, eğer Country silinirse League'de arka planda otomatik olarak silinmelidir. Bu mantığa bakıldığında, elimizdeki en uygun seçenek Cascade 2 olur yani, cascade={"remove"} veya orphanRemoval=true Country entity üzerinde ayarlı olacaktır.


Okunacak notlar