Complete re-write of "Transitive persistence / Cascade Operations" (#6219)
Follow-up of https://github.com/doctrine/doctrine2/pull/6171
This commit is contained in:
parent
4e304df495
commit
63e4eea9e2
@ -407,31 +407,14 @@ There are two approaches to handle this problem in your code:
|
|||||||
Transitive persistence / Cascade Operations
|
Transitive persistence / Cascade Operations
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
When working with many associated entities, you sometimes don't want to "expose" all entities to your PHP application.
|
Doctrine 2 provides a mechanism for transitive persistence through cascading of certain operations.
|
||||||
Therefore Doctrine 2 provides a mechanism for transitive persistence through cascading of certain operations.
|
|
||||||
Each association to another entity or a collection of
|
Each association to another entity or a collection of
|
||||||
entities can be configured to automatically cascade the following operations to the associated entities:
|
entities can be configured to automatically cascade the following operations to the associated entities:
|
||||||
``persist``, ``remove``, ``merge``, ``detach``, ``refresh`` or ``all``.
|
``persist``, ``remove``, ``merge``, ``detach``, ``refresh`` or ``all``.
|
||||||
|
|
||||||
.. note::
|
The main use case for ``cascade: persist`` is to avoid "exposing" associated entities to your PHP application.
|
||||||
|
Continuing with the User-Comment example of this chapter, this is how the creation of a new user and a new
|
||||||
Cascade operations are performed in memory. That means collections and related entities
|
comment might look like in your controller (without ``cascade: persist``):
|
||||||
are fetched into memory, even if they are still marked as lazy when
|
|
||||||
the cascade operation is about to be performed. However this approach allows
|
|
||||||
entity lifecycle events to be performed for each of these operations.
|
|
||||||
|
|
||||||
However, pulling objects graph into memory on cascade can cause considerable performance
|
|
||||||
overhead, especially when cascading collections are large. Makes sure
|
|
||||||
to weigh the benefits and downsides of each cascade operation that you define.
|
|
||||||
|
|
||||||
To rely on the database level cascade operations for the delete operation instead, you can
|
|
||||||
configure each join column with the **onDelete** option. See the respective
|
|
||||||
mapping driver chapters for more information.
|
|
||||||
|
|
||||||
The following example is an extension to the User-Comment example
|
|
||||||
of this chapter. Suppose in our application a user is created
|
|
||||||
whenever they write their first comment. In this case we would use the
|
|
||||||
following code:
|
|
||||||
|
|
||||||
.. code-block:: php
|
.. code-block:: php
|
||||||
|
|
||||||
@ -444,34 +427,36 @@ following code:
|
|||||||
$em->persist($myFirstComment); // required, if `cascade: persist` isn't set
|
$em->persist($myFirstComment); // required, if `cascade: persist` isn't set
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
Even if you *persist* a new User that contains our new Comment this
|
Note that the Comment entity is instantiated right here in the controller.
|
||||||
code requires an explicit call to
|
To avoid this, ``cascade: persist`` allows you to "hide" the Comment entity from the controller,
|
||||||
``EntityManager#persist($myFirstComment)``. Doctrine 2 does not
|
only accessing it through the User entity:
|
||||||
cascade the persist operation to all nested entities that are new
|
|
||||||
as well.
|
|
||||||
|
|
||||||
More complicated is the deletion of all user's comments when the user is
|
|
||||||
removed from the system:
|
|
||||||
|
|
||||||
.. code-block:: php
|
.. code-block:: php
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
$user = $em->find('User', $deleteUserId);
|
// User entity
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
private $id;
|
||||||
|
private $comments;
|
||||||
|
|
||||||
foreach ($user->getAuthoredComments() as $comment) {
|
public function __construct()
|
||||||
$em->remove($comment);
|
{
|
||||||
|
$this->id = User::new();
|
||||||
|
$this->comments = new ArrayCollection();
|
||||||
}
|
}
|
||||||
$em->remove($user);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
Without the loop over all the authored comments Doctrine would use
|
public function comment(string $text, DateTimeInterface $time) : void
|
||||||
an UPDATE statement only to set the foreign key to NULL and only
|
{
|
||||||
the User would be deleted from the database during the
|
$newComment = Comment::create($text, $time);
|
||||||
flush()-Operation.
|
$newComment->setUser($this);
|
||||||
|
$this->comments->add($newComment);
|
||||||
|
}
|
||||||
|
|
||||||
To have Doctrine handle both cases automatically we can change the
|
// ...
|
||||||
``User#commentsAuthored`` property to cascade both the "persist"
|
}
|
||||||
and the "remove" operation.
|
|
||||||
|
If you then set up the cascading to the ``User#commentsAuthored`` property...
|
||||||
|
|
||||||
.. code-block:: php
|
.. code-block:: php
|
||||||
|
|
||||||
@ -488,30 +473,51 @@ and the "remove" operation.
|
|||||||
//...
|
//...
|
||||||
}
|
}
|
||||||
|
|
||||||
Since ``cascade: persist`` is configured for the ``User#commentsAuthored``
|
...you can now create a user and an associated comment like this:
|
||||||
association, you can now create a user and persist their comments as follows:
|
|
||||||
|
|
||||||
.. code-block:: php
|
.. code-block:: php
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
$user = new User();
|
$user = new User();
|
||||||
$myFirstComment = new Comment();
|
$user->comment('Lorem ipsum', new DateTime());
|
||||||
$user->addComment($myFirstComment);
|
|
||||||
$myFirstComment->setUser($user);
|
|
||||||
|
|
||||||
$em->persist($user);
|
$em->persist($user);
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This code does not associate the newly created comment with the user.
|
The idea of ``cascade: persist`` is not to save you any lines of code in the controller.
|
||||||
To achieve this, you *always* have to call ``$myFirstComment->setUser($user);`` –
|
If you instantiate the comment object in the controller (i.e. don't set up the user entity as shown above),
|
||||||
no matter if ``cascade: persist`` is set or not.
|
even with ``cascade: persist`` you still have to call ``$myFirstComment->setUser($user);``.
|
||||||
|
|
||||||
|
Thanks to ``cascade: remove``, you can easily delete a user and all linked comments without having to loop through them:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$user = $em->find('User', $deleteUserId);
|
||||||
|
|
||||||
|
$em->remove($user);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Cascade operations are performed in memory. That means collections and related entities
|
||||||
|
are fetched into memory (even if they are marked as lazy) when
|
||||||
|
the cascade operation is about to be performed. However this approach allows
|
||||||
|
entity lifecycle events to be performed for each of these operations.
|
||||||
|
|
||||||
|
However, pulling objects graph into memory on cascade can cause considerable performance
|
||||||
|
overhead, especially when the cascaded collections are large. Make sure
|
||||||
|
to weigh the benefits and downsides of each cascade operation that you define.
|
||||||
|
|
||||||
|
To rely on the database level cascade operations for the delete operation instead, you can
|
||||||
|
configure each join column with :doc:`the **onDelete** option <working-with-objects>`.
|
||||||
|
|
||||||
Even though automatic cascading is convenient, it should be used
|
Even though automatic cascading is convenient, it should be used
|
||||||
with care. Do not blindly apply ``cascade=all`` to all associations as
|
with care. Do not blindly apply ``cascade=all`` to all associations as
|
||||||
it will unnecessarily degrade the performance of your application.
|
it will unnecessarily degrade the performance of your application.
|
||||||
For each cascade operation that gets activated Doctrine also
|
For each cascade operation that gets activated, Doctrine also
|
||||||
applies that operation to the association, be it single or
|
applies that operation to the association, be it single or
|
||||||
collection valued.
|
collection valued.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user