Last active
August 20, 2025 14:56
-
Star
(118)
You must be signed in to star a gist -
Fork
(7)
You must be signed in to fork a gist
-
-
Save pylebecq/f844d1f6860241d8b025 to your computer and use it in GitHub Desktop.
Revisions
-
pylebecq revised this gist
Feb 8, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -119,7 +119,7 @@ $em->remove($a); $em->flush(); // Works ``` The `cascade={"remove"}` option is well suited when we want our objects to fulfill the following sentence: *"When we remove a `A` object, we want all `B` objects referencing this `A` object to also be removed"*. It works exactly as we would expect a `onDelete=CASCADE` to work when specified in our database, except that it takes place in your PHP application instead of in the database. We could achieve the exact same result by removing the `cascade={"remove"}` and putting a `onDelete="CASCADE"` in `B`: ``` php class A -
pylebecq revised this gist
Feb 12, 2015 . 1 changed file with 14 additions and 14 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -33,7 +33,7 @@ class B } ``` And then let's initialise some instances of these entities to be able to use them in the example: ``` php // First, we create a correct association between instances of theses entities @@ -69,14 +69,14 @@ Ok, let's now try different operations on our data. ## Understanding cascade={"remove"} First, if we wanted to remove `$a`, we could do the following: ``` php $em->remove($a); $em->flush(); ``` This does not work. This result in the following exception: ``` [Doctrine\DBAL\DBALException] @@ -86,16 +86,16 @@ This result in the following exception: E5358` FOREIGN KEY (`a_id`) REFERENCES `A` (`id`)) ``` This is because by default, the **database** will prevent deletions of objects `A` which are referenced by `B` objects. To make this work, we could instruct to remove `$b`: ``` php $em->remove($a); $em->remove($b); $em->flush(); // Works ``` However, if we have many relationships with other objects not represented in our example, this could be painful. So, if we wanted to remove all `B` objects referencing `A` objects without explicitely removing them, we could change `A` to look like this: ``` php class A @@ -119,7 +119,7 @@ $em->remove($a); $em->flush(); // Works ``` The `cascade={"remove"}` option is well suited when we want our objects to fulfill the following sentence: *"When we remove a `A` object, we want all `B` objects referencing this `A` object to also be removed"*. It works exactly as we would expect a `onDelete=CASCADE` to work when specified in our database, except that it takes place in your PHP application instead of in the database. We could achieve the exact same result by removing the `cascade={"persist"}` and putting a `onDelete="CASCADE"` in `B`: ``` php class A @@ -188,14 +188,14 @@ class B } ``` Now, we don't want to remove A, but we want to remove the relationship between `$a` and `$b`: ``` php $a->setB(null); $em->flush(); // No error, but does nothing ``` The data is the database are still the same as in the beginning: ``` MariaDB [playground]> select * from a; select * from b; @@ -214,9 +214,9 @@ MariaDB [playground]> select * from a; select * from b; 1 row in set (0.00 sec) ``` This might be a surprise but the code above **does nothing** because we are modifying the inverse side of the relationship. And modifying only the inverse side of the relationship will not do anything. Owning and inverse sides are not in the scope of this article so if you need to know more, please read carefully the [documentation](http://doctrine-orm.readthedocs.org/en/latest/reference/unitofwork-associations.html). This is were the `orphanRemoval` option can be used. It will instruct Doctrine to look at the inverse side of the relationship to determine if the `B` object is still referenced by the `A` object. And if it is not the case, then the `B` object is considered orphan, and having this option to `true` means we don't want orphans. Thus, Doctrine will remove it from the database: ``` php class A @@ -262,8 +262,8 @@ MariaDB [playground]> select * from a; select * from b; Empty set (0.00 sec) ``` We could achieve the same result simply by explicitely removing `$b`. The orphanRemoval option is particularely useful when not used with OneToOne relationships but rather OneToMany ones, because all we need to do is to add/remove objects from the collection and Doctrine will figure out if the objects need to be persisted (you will need the `cascade={"persist"}` for that if there are some new entities added) or removed. One funny thing to note is that `orphanRemoval` sometimes achieve the same result as cascade={"remove"}, because when using it and removing `$a`, Doctrine is smart enough to know the `$b` object will result in an orphan and so it will issue a query to remove it. You end up with both `A` and `B` object being removed, as when using the cascade. -
pylebecq revised this gist
Feb 12, 2015 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -155,7 +155,7 @@ $em->flush(); // Works They achieve the same result but differently: - `cascade={"remove"}` will issue two queries to the database: the first one will instruct to remove `$b` and the second will instruct to remove `$a`. - `onDelete="CASCADE"` will issue only one query which will instruct to remove `$a` and the database knows it must also remove `$b` objects referencing `$a`. ## Understanding orphanRemoval=true -
pylebecq renamed this gist
Feb 12, 2015 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
pylebecq created this gist
Feb 12, 2015 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,269 @@ # What's the difference between cascade="remove" and orphanRemoval=true in Doctrine 2 TLDR: The `cascade={"remove"}` is like a "software" `onDelete="CASCADE"`, and will remove objects from the database only when an explicit call to `$em->remove()` occurs. Thus, it could result in more than one object being deleted. `orphanRemoval` can remove objects from the database even if there was no explicit call to `->remove()`. I answered this question a few times to different people so I will try to sum things up in this Gist. Let's take two entities `A` and `B` as an example. I will use a OneToOne relationship in this example but it works exactly the same with OneToMany relationships. ``` php class A { // [...] /** * @ORM\OneToOne(targetEntity="B", mappedBy="a") */ protected $b; // [...] } class B { // [...] /** * @ORM\OneToOne(targetEntity="A", inversedBy="b") * @ORM\JoinColumn(name="a_id", referencedColumnName="id") */ protected $a; // [...] } ``` Let's figure out what these options actually do. ``` php // First, we create a correct association between instances of theses entities $a = new A(); $b = new B(); $a->setB($b); $b->setA($a); $em->persist($a); $em->persist($b); $em->flush(); ``` At this point, we have this in our database: ``` MariaDB [playground]> select * from a; select * from b; +----+ | id | +----+ | 1 | +----+ 1 row in set (0.00 sec) +----+------+ | id | a_id | +----+------+ | 1 | 1 | +----+------+ 1 row in set (0.00 sec) ``` Ok, let's now try different operations on our data. ## Understanding cascade={"remove"} First we want to remove `$a`: ``` php $em->remove($a); $em->flush(); // Does not work ``` This result in the following exception: ``` [Doctrine\DBAL\DBALException] An exception occurred while executing 'DELETE FROM A WHERE id = ?' with params [1]: SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a pare nt row: a foreign key constraint fails (`playground`.`b`, CONSTRAINT `FK_4AD0CF313BD E5358` FOREIGN KEY (`a_id`) REFERENCES `A` (`id`)) ``` This is because by default, the *database* will prevent deletions of objects `A` which are referenced by `B` objects. So you could ask to remove `$b`, and everything is fine: ``` php $em->remove($a); $em->remove($b); $em->flush(); // Works ``` But if you have many relationships with other objects not represented in our example, this could be painful. So, if you want to remove all `B` objects referencing `A` objects without explicitely removing them, you can change `A` to look like this: ``` php class A { // [...] /** * @ORM\OneToOne(targetEntity="B", mappedBy="a", cascade={"remove"}) */ protected $b; // [...] } ``` This asks Doctrine to cascade the remove instruction to objects `B` referencing the `A` object we want to remove. So our first example, which was not working, is now working thanks to the `cascade={"remove"}` on the relationship with `B`. ``` php $em->remove($a); $em->flush(); // Works ``` So, when you're saying *"When I remove a `A` object, I want all `B` objects referencing this `A` object to also be removed"*, then the correct solution is to use the `cascade={"remove"}` option. It works exactly as you would a `onDelete=CASCADE` to work when specified in your database, except that it takes place in your PHP application instead of in the database. You could achieve the exact same result by removing the `cascade={"persist"}` and putting a `onDelete="CASCADE"` in `B`: ``` php class A { // [...] /** * @ORM\OneToOne(targetEntity="B", mappedBy="a") */ protected $b; // [...] } class B { // [...] /** * @ORM\OneToOne(targetEntity="A", inversedBy="b") * @ORM\JoinColumn(name="a_id", referencedColumnName="id", onDelete="CASCADE") */ protected $a; // [...] } ``` ``` php $em->remove($a); $em->flush(); // Works ``` They achieve the same result but differently: - `cascade={"remove"}` will issue two queries to the database: the first one will instruct to remove `$b` and the second will instruct to remove `$a`. - `onDelete="CASCADE" will issue only one query which will instruct to remove `$a` and the database knows it must also remove `$b` objects referencing `$a`. ## Understanding orphanRemoval=true Let's get back to our original scenario: ``` php class A { // [...] /** * @ORM\OneToOne(targetEntity="B", mappedBy="a") */ protected $b; // [...] } class B { // [...] /** * @ORM\OneToOne(targetEntity="A", inversedBy="b") * @ORM\JoinColumn(name="a_id", referencedColumnName="id") */ protected $a; // [...] } ``` Now, I don't want to remove A, but I just want to remove the relationship between `$a` and `$b`: ``` php $a->setB(null); $em->flush(); // No error, but does nothing ``` The data is the database are still like in the beginning: ``` MariaDB [playground]> select * from a; select * from b; +----+ | id | +----+ | 1 | +----+ 1 row in set (0.00 sec) +----+------+ | id | a_id | +----+------+ | 1 | 1 | +----+------+ 1 row in set (0.00 sec) ``` This might be a surprise but the code above *does nothing* because you are setting the inverse side of the relationship. And setting only the inverse side of the relationship will do anything. Owning and inverse sides are not in the scope of this article so if you need to know more, please read carefully the [documentation](http://doctrine-orm.readthedocs.org/en/latest/reference/unitofwork-associations.html). This is were the `orphanRemoval` option can be used. It will instruct Doctrine to look the inverse side of the relationship to determine if the `B` object is still referenced by the `A` object. And if it is not the case, then the `B` object is considered orphan, and having this option to `true` means you don't want orphans. Thus, Doctrine will remove it from the database: ``` php class A { // [...] /** * @ORM\OneToOne(targetEntity="B", mappedBy="a", orphanRemoval=true) */ protected $b; // [...] } class B { // [...] /** * @ORM\OneToOne(targetEntity="A", inversedBy="b") * @ORM\JoinColumn(name="a_id", referencedColumnName="id") */ protected $a; // [...] } ``` ``` php $a->setB(null); $em->flush(); // Works, B is removed ``` ``` MariaDB [playground]> select * from a; select * from b; +----+ | id | +----+ | 1 | +----+ 1 row in set (0.00 sec) Empty set (0.00 sec) ``` You could achieve the same result simply by explicitely removing `$b`. The orphanRemoval option is particularely useful when not using OneToOne relationships but rather OneToMany ones, because all you need to do is to add/remove objects from the collection and Doctrine will figure out if the objects need to be persisted (you will need the `cascade={"persist"}` for that if there are some new entities added) or removed. One funny thing to note is that `orphanRemoval` sometimes achieve the same result as cascade={"remove"}, because when using it and removing `$a`, Doctrine is smart enough to know the `$b` object will result in an orphan and so it will issue a query to remove it. You end up with both `A` and `B` object being removed, as when using the cascade.