Skip to main content

Lifecycle

This article explains how Entity objects are managed by the EntityManager and synchronized with the database.

Persistence Context​

Each EntityManager forms a Persistence Context. The Persistence Context manages all Entity objects that were loaded via the EntityManager. Within the Persistence Context, only one instance can exist per Entity and Id.

If you query the same Entity object twice (whether via EntityManager::find(), the Criteria API or NQL, the EntityManager returns the same instance of this Entity both times.

$article = $em->find(Article::getClass(), $id);

$article2 = $em->createSimpleCriteria(
Article::getClass(),
array('id' => $id))
->toQuery()
->fetchSingle();

var_dump($article === $article2); // bool(true)

EntityManager::find() first checks whether the Entity object to be loaded is already managed by the Persistence Context and, if successful, returns it directly without accessing the database.

Queries via the Criteria API or NQL, on the other hand, always result in a database query. This also applies when all returned objects are already managed by the Persistence Context. The properties of these Entity objects are not overwritten with the current values from the database. Changes that have not yet been written to the database are therefore preserved.

Changes to Entity objects are only written to the database on a flush event. A flush event can be triggered via EntityManager::flush() or by committing a transaction.

Entity States​

Entity objects can have one of the following states:

  • NEW
    A newly created Entity object that has never belonged to a Persistence Context.

  • MANAGED
    A persistent Entity object for which a corresponding entry exists in the database, or a newly created Entity object on which the Persist operation has been applied. All changes to an Entity object with state MANAGED are automatically written to the database on the next flush event (UPDATE statements are executed).

  • DETACHED
    Entity objects that are no longer managed by the Persistence Context. Changes to such Entity objects are no longer written to the database. An Entity object is also recognized as DETACHED if it originates from another Persistence Context (e.g. if it was loaded via a different EntityManager).

  • REMOVED
    Entity objects on which the Remove operation has been applied.

Operations​

Using the operations Persist, Merge, Remove and Detach you can change the state of an Entity object.

Entity States

Entity States

Persist​

The Persist operation is applied to Entity objects via EntityManager::persist() or passed on to them via relationship properties with CascadeType::PERSIST.

The affected Entity objects subsequently get the state MANAGED. This operation only makes sense for Entity objects with state NEW. These Entity objects are written to the database on the next flush event (INSERT statements are executed).

The flush event also causes the Persist operation to be applied to all Entity objects with state MANAGED that are managed by the Persistence Context. This operation has no effect on Entity objects with state MANAGED, but it is still passed on to relationship properties with CascadeType::PERSIST.

Applying this operation to Entity objects with state DETACHED or REMOVED leads to an exception.

public function doNewArticle(EntityManager $em) {

$this->beginTransaction();

$newArticle = new Article();
$newArticle->setTitle('Lorem ipsum');
$newArticle->setText('Lorem ipsum dolor');

$em->persist($newArticle);

$this->commit();

// rest of controller method body
}

Persist operations must be executed within a transaction.

Merge​

The Merge operation is applied to Entity objects via EntityManager::merge() or passed on to them via relationship properties with CascadeType::MERGE.

The Merge operation copies the properties of an Entity object with state DETACHED to the properties of an Entity object with state MANAGED that is managed by the respective Persistence Context. If the Persistence Context does not manage an equivalent Entity object at this point in time, a new one is created.

EntityManager::merge() returns the Entity object with state MANAGED. Relationship properties with CascadeType::MERGE are overwritten with this Entity object or these Entity objects.

This operation has no effect on Entity objects with state MANAGED (EntityManager::merge() e.g. returns the same instance), but it is still passed on to relationship properties with CascadeType::MERGE.

Applying this operation to Entity objects with state DETACHED or REMOVED leads to an exception.

$managedArticle = $em->merge($detachedArticle);

Merge operations must be executed within a transaction.

Remove​

The Remove operation is applied to Entity objects via EntityManager::remove() or passed on to them via relationship properties with CascadeType::REMOVE or with enabled Orphan Removal.

The affected Entity objects subsequently get the state REMOVED.

This operation can be applied to Entity objects with state MANAGED. These are deleted from the database on the next flush event (DELETE statements are executed).

This operation has no effect on Entity objects with state REMOVED, but it is still passed on to relationship properties with CascadeType::REMOVE.

Applying this operation to Entity objects with state NEW or DETACHED leads to an exception.

$em->remove($article);

Remove operations must be executed within a transaction.

Refresh​

The Refresh operation is applied to Entity objects via EntityManager::refresh() or passed on to them via relationship properties with CascadeType::REFRESH.

All properties are updated with the values from the database. Changes that have not yet been persisted are overwritten in the process.

This operation can be applied to Entity objects with state MANAGED. Applying the Refresh operation to Entity objects with state NEW, REMOVED or DETACHED leads to an exception.

$em->refresh($article);

Detach​

The Detach operation is applied to Entity objects via EntityManager::detach() or passed on to them via relationship properties with CascadeType::DETACH.

The affected Entity objects subsequently get the state DETACHED.

This operation can be applied to Entity objects with state MANAGED or REMOVED. All changes that have not yet been written to the database are discarded (including the deletion of Entity objects).

This operation has no effect on Entity objects with state NEW or DETACHED, but it is still passed on to relationship properties with CascadeType::DETACH.

$em->detach($article);

EntityManager::clear() empties the entire Persistence Context. As a result, all Entity objects managed by it get the state DETACHED.

Callbacks​

You can implement callback methods for lifecycle events directly in the Entity. These methods are magic.

The following methods are available:

MethodDescription
_prePersist()Is executed before a Persist operation is applied to the affected Entity object (only if it has the state NEW), e.g. by EntityManager::persist().
_postPersist()Is executed after a flush event when the Entity object has been newly persisted (after the required INSERT statements have been executed).
_preRemove()Is executed before a Remove operation is applied to the affected Entity object (e.g. EntityManager::remove()).
_postRemove()Is executed after a flush event when the Entity object has been deleted from the database (after the required DELETE statements have been executed).
_preUpdate()Is executed before a flush event when changes to the Entity object have been detected. Changes to transient properties are not detected. Changes made within this method are taken into account during the subsequent flush.
_postUpdate()Is executed after a flush event when changes to the Entity object have been saved (after the required UPDATE statements have been executed).
_postLoad()Is executed after the affected Entity has been loaded from the database (also applies to the Refresh operation).

Callback methods can have any visibility.

class Article extends ObjectAdapter {

private $lastMod;

private function _prePersist() {
$this->lastMod = new \DateTime();
}

private function _preUpdate() {
$this->lastMod = new \DateTime();
}

public function setLastMod(\DateTime $lastMod) {
$this->lastMod = $lastMod;
}

/**
* @return \DateTime
*/
public function getLastMod() {
return $this->lastMod;
}

// rest of class body
}

In this example, we add a new property $lastMod of type DateTime to the Entity Article. The property is intended to contain the date and time of the last modification. To implement this, we use the callback methods _prePersist() and _preUpdate() to update this property.

You can also outsource callback methods to Entity Listeners. You annotate these with n2n\persistence\orm\annotation\AnnoEntityListeners.

#[EntityListeners(LastModListener::class)]
class Article extends ObjectAdapter {
public \DateTime $lastMod;
// rest of class body
}

AnnoEntityListeners expects the ReflectionClass objects of the Entity Listeners. Therefore, your Entity Listeners should also extend n2n\reflection\ObjectAdapter.

#[Lookupable]
class LastModListener extends ObjectAdapter {

private function _prePersist(Article $article) {
$article->setLastMod(new \DateTime());
}

private function _preUpdate(Article $article) {
$article->setLastMod(new \DateTime());
}
}

You can have the magic callback methods pass the affected Entity object as a parameter. Entity Listener classes are also initialized magically (_init() is available).