Bu yazıda cascade={"remove"} ve orphanRemoval=true doctrine özelliklerinin bidirectional one-to-many 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 League
{
/**
* @ORM\OneToMany(targetEntity="Team", mappedBy="league")
*/
private $teams;

public function __construct()
{
$this->teams = new ArrayCollection();
}
}

class Team
{
/**
* @ORM\ManyToOne(targetEntity="League", inversedBy="teams")
* @ORM\JoinColumn(name="league_id", referencedColumnName="id", nullable=false)
*/
private $league;
}

`league` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
)

`team` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`league_id` int(11) NOT NULL,
`name` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
KEY `IDX_C4E0A61F58AFC4DE` (`league_id`),
CONSTRAINT `FK_C4E0A61F58AFC4DE` FOREIGN KEY (`league_id`) REFERENCES `league` (`id`)
)

mysql> SELECT
-> `league`.`id` AS LeagueID,
-> `league`.`name` AS LeagueName,
-> `team`.`id` AS TeamID,
-> `team`.`name` AS TeamName
-> FROM `league`
-> INNER JOIN `team` ON `league`.`id` = `team`.`league_id`
-> WHERE
-> `team`.`league_id` = 1;
+----------+------------+--------+-------------+
| LeagueID | LeagueName | TeamID | TeamName |
+----------+------------+--------+-------------+
| 1 | Super Lig | 1 | Fenerbahce |
| 1 | Super Lig | 2 | Galatasaray |
| 1 | Super Lig | 3 | Besiktas |
+----------+------------+--------+-------------+
3 rows in set (0.00 sec)

Testler


Cascade 1) Cascade yok


class League
{
/**
* @ORM\OneToMany(targetEntity="Team", mappedBy="league")
*/
private $teams;

public function __construct()
{
$this->teams = new ArrayCollection();
}
}

class Team
{
/**
* @ORM\ManyToOne(targetEntity="League", inversedBy="teams")
* @ORM\JoinColumn(name="league_id", referencedColumnName="id", nullable=false)
*/
private $league;
}

League silme: Team'in varlığı League'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 league WHERE id = ? LIMIT 1 ["1"] []
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM league WHERE id = ? [1] []
doctrine.DEBUG: "ROLLBACK" [] []

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


$this->entityManager->remove($entity);
$this->entityManager->flush();

doctrine.DEBUG: SELECT ... FROM team WHERE id = ? LIMIT 1 ["1"] []
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM team WHERE id = ? [1] []
doctrine.DEBUG: "COMMIT" [] []

Cascade 2) Cascade League


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


class League
{
/**
* @ORM\OneToMany(targetEntity="Team", mappedBy="league", cascade={"remove"})
*/
private $teams;

public function __construct()
{
$this->teams = new ArrayCollection();
}
}

class Team
{
/**
* @ORM\ManyToOne(targetEntity="League", inversedBy="teams")
* @ORM\JoinColumn(name="league_id", referencedColumnName="id", nullable=false)
*/
private $league;
}

League silme: League ve Team silinir.


$this->entityManager->remove($entity);
$this->entityManager->flush();

doctrine.DEBUG: SELECT ... FROM league WHERE id = ? LIMIT 1 ["1"] []
doctrine.DEBUG: SELECT ... FROM team WHERE league_id = ? [1] []
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM team WHERE id = ? [1] []
doctrine.DEBUG: DELETE FROM team WHERE id = ? [2] []
doctrine.DEBUG: DELETE FROM team WHERE id = ? [3] []
doctrine.DEBUG: DELETE FROM league WHERE id = ? [1] []
doctrine.DEBUG: "COMMIT" [] []

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


$this->entityManager->remove($entity);
$this->entityManager->flush();

doctrine.DEBUG: SELECT ... FROM team WHERE id = ? LIMIT 1 ["1"] []
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM team WHERE id = ? [1] []
doctrine.DEBUG: "COMMIT" [] []

Cascade 3) Cascade Team


Eğer cascade={"remove"} yerine orphanRemoval=true kullanmış olsaydınız [Creation Error] The annotation @ORM\ManyToOne declared on property Team::$league does not have a property named "orphanRemoval". Available properties: targetEntity, cascade, fetch, inversedBy hatası alırdınız.


class League
{
/**
* @ORM\OneToMany(targetEntity="Team", mappedBy="league")
*/
private $teams;

public function __construct()
{
$this->teams = new ArrayCollection();
}
}

class Team
{
/**
* @ORM\ManyToOne(targetEntity="League", inversedBy="teams", cascade={"remove"})
* @ORM\JoinColumn(name="league_id", referencedColumnName="id", nullable=false)
*/
private $league;
}

League silme: Team'in varlığı League'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 league WHERE id = ? LIMIT 1 ["1"] []
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM league WHERE id = ? [1] []
doctrine.DEBUG: "ROLLBACK" [] []

Team silme: Tüm Team'lerin varlığı League'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 team WHERE id = ? LIMIT 1 ["1"] []
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM team WHERE id = ? [1] []
doctrine.DEBUG: DELETE FROM league WHERE id = ? [1] []
doctrine.DEBUG: "ROLLBACK" [] []

Cascade 4) Cascade League ve Team


Eğer cascade={"remove"} yerine [Creation Error] The annotation @ORM\ManyToOne declared on property Team::$league does not have a property named "orphanRemoval". Available properties: targetEntity, cascade, fetch, inversedBy hatası alırdınız.


class League
{
/**
* @ORM\OneToMany(targetEntity="Team", mappedBy="league", cascade={"remove"})
*/
private $teams;

public function __construct()
{
$this->teams = new ArrayCollection();
}
}

class Team
{
/**
* @ORM\ManyToOne(targetEntity="League", inversedBy="teams", cascade={"remove"})
* @ORM\JoinColumn(name="league_id", referencedColumnName="id", nullable=false)
*/
private $league;
}

League silme: League ve Team silinir.


$this->entityManager->remove($entity);
$this->entityManager->flush();

doctrine.DEBUG: SELECT ... FROM league WHERE id = ? LIMIT 1 ["1"] []
doctrine.DEBUG: SELECT ... FROM team WHERE league_id = ? [1] []
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM team WHERE id = ? [1] []
doctrine.DEBUG: DELETE FROM team WHERE id = ? [2] []
doctrine.DEBUG: DELETE FROM team WHERE id = ? [3] []
doctrine.DEBUG: DELETE FROM league WHERE id = ? [1] []
doctrine.DEBUG: "COMMIT" [] []

Team silme: Team ve League silinir.


$this->entityManager->remove($entity);
$this->entityManager->flush();

doctrine.DEBUG: SELECT ... FROM team WHERE id = ? LIMIT 1 ["1"] []
doctrine.DEBUG: SELECT ... FROM league WHERE id = ? [1] []
doctrine.DEBUG: SELECT ... FROM team WHERE league_id = ? [1] []
doctrine.DEBUG: "START TRANSACTION" [] []
doctrine.DEBUG: DELETE FROM team WHERE id = ? [2] []
doctrine.DEBUG: DELETE FROM team WHERE id = ? [3] []
doctrine.DEBUG: DELETE FROM team WHERE id = ? [1] []
doctrine.DEBUG: DELETE FROM league WHERE id = ? [1] []
doctrine.DEBUG: "COMMIT" [] []

Sonuç


Bu örnekte League bağımsız bir entitydir yani Team'e ne olursa olsun, arka planda League'nin otomatik olarak silinmemesi gerekir. Bununla birlikte Team'in varlığı League'nin varlığına bağlı olduğu için, eğer League silinirse Team'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 League entity üzerinde ayarlı olacaktır.


Okunacak notlar