Change Tracking Policies
Change tracking is the process of determining what has changed in managed entities since the last time they were synchronized with the database.
Doctrine provides 3 different change tracking policies, each having its particular advantages and disadvantages. The change tracking policy can be defined on a per-class basis (or more precisely, per-hierarchy).
Deferred Implicit
The deferred implicit policy is the default change tracking policy and the most convenient one. With this policy, Doctrine detects the changes by a property-by-property comparison at commit time and also detects changes to entities or new entities that are referenced by other managed entities ("persistence by reachability"). Although the most convenient policy, it can have negative effects on performance if you are dealing with large units of work (see "Understanding the Unit of Work"). Since Doctrine can't know what has changed, it needs to check all managed entities for changes every time you invoke EntityManager#flush(), making this operation rather costly.
Deferred Explicit
The deferred explicit policy is similar to the deferred implicit policy in that it detects changes through a property-by-property comparison at commit time. The difference is that Doctrine ORM only considers entities that have been explicitly marked for change detection through a call to EntityManager#persist(entity) or through a save cascade. All other entities are skipped. This policy therefore gives improved performance for larger units of work while sacrificing the behavior of "automatic dirty checking".
Therefore, flush() operations are potentially cheaper with this policy. The negative aspect this has is that if you have a rather large application and you pass your objects through several layers for processing purposes and business tasks you may need to track yourself which entities have changed on the way so you can pass them to EntityManager#persist().
This policy can be configured as follows:
Notify
The notify change tracking policy is deprecated and will be removed in ORM 3.0. (Details) |
This policy is based on the assumption that the entities notify
interested listeners of changes to their properties. For that
purpose, a class that wants to use this policy needs to implement
the NotifyPropertyChanged
interface from the Doctrine
namespace. As a guideline, such an implementation can look as
follows:
1 <?php
use Doctrine\Persistence\NotifyPropertyChanged,
Doctrine\Persistence\PropertyChangedListener;
#[Entity]
#[ChangeTrackingPolicy('NOTIFY')]
class MyEntity implements NotifyPropertyChanged
{
// ...
private array $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener): void
{
$this->_listeners[] = $listener;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Then, in each property setter of this class or derived classes, you
need to notify all the PropertyChangedListener
instances. As an
example we add a convenience method on MyEntity
that shows this
behaviour:
1 <?php
// ...
class MyEntity implements NotifyPropertyChanged
{
// ...
protected function _onPropertyChanged($propName, $oldValue, $newValue): void
{
if ($this->_listeners) {
foreach ($this->_listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
}
public function setData($data): void
{
if ($data != $this->data) {
$this->_onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
You have to invoke _onPropertyChanged
inside every method that
changes the persistent state of MyEntity
.
The check whether the new value is different from the old one is not mandatory but recommended. That way you also have full control over when you consider a property changed.
If your entity contains an embeddable, you will need to notify separately for each property in the embeddable when it changes for example:
1 <?php
// ...
class MyEntity implements NotifyPropertyChanged
{
public function setEmbeddable(MyValueObject $embeddable): void
{
if (!$embeddable->equals($this->embeddable)) {
// notice the entityField.embeddableField notation for referencing the property
$this->_onPropertyChanged('embeddable.prop1', $this->embeddable->getProp1(), $embeddable->getProp1());
$this->_onPropertyChanged('embeddable.prop2', $this->embeddable->getProp2(), $embeddable->getProp2());
$this->embeddable = $embeddable;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
This would update all the fields of the embeddable, you may wish to implement a diff method on your embedded object which returns only the changed fields.
The negative point of this policy is obvious: You need implement an
interface and write some plumbing code. But also note that we tried
hard to keep this notification functionality abstract. Strictly
speaking, it has nothing to do with the persistence layer and the
Doctrine ORM or DBAL. You may find that property notification
events come in handy in many other scenarios as well. As mentioned
earlier, the Doctrine\Common
namespace is not that evil and
consists solely of very small classes and interfaces that have
almost no external dependencies (none to the DBAL and none to the
ORM) and that you can easily take with you should you want to swap
out the persistence layer. This change tracking policy does not
introduce a dependency on the Doctrine DBAL/ORM or the persistence
layer.
The positive point and main advantage of this policy is its effectiveness. It has the best performance characteristics of the 3 policies with larger units of work and a flush() operation is very cheap when nothing has changed.