Lifecycle

ORM

Dieser Artikel erläutert, wie Entity-Objekte vom EntityManger verwaltet und mit der Datenbank synchronisiert werden.

  1. Persistence Context
  2. Entity States
  3. Operationen
  4. Callbacks

Persistence Context

Jeder EntityManager bildet einen Persistence Context. Der Persistence Context verwaltet alle Entity-Objekte, die über den EntityManager geladen wurden. Im Persistence Context kann pro Entity und Id immer nur eine Instanz existieren. Fragst du zwei Mal das selbe Entity-Objekt ab (egal ob über EntityManager::find(), die Criteria API oder NQL), liefert dir der EntityManager also beide Male die selbe Instanz dieser Entity zurück.

        $article = $em->find(Article::getClass(), $id);
        $article2 = $em->createSimpleCriteria(Article::getClass(), array('id' => $id))
                ->toQuery()->fetchSingle();
        
        var_dump($article === $article2); // bool(true)

EntityManager::find() überprüft zuerst, ob das zu ladende Entity-Objekt bereits vom Persistence Context verwaltet wird und gibt im Erfolgsfall dieses direkt zurück, ohne auf die Datenbank zuzugreifen. Abfragen über die  Criteria API oder NQL führen aber immer zu einer Datenbank-Abfrage. Dies gilt auch, wenn alle Objekte, die zurückgegeben werden, bereits im Persistence Context verwaltet werden. Die Eigenschaften dieser Entity-Objekte werden dabei nicht mit den aktuellen Werten aus der Datenbank überschrieben. Änderungen, die noch nicht in die Datenbank geschrieben wurden, bleiben also bestehen.

Änderungen an Entity-Objekten werden erst bei einem Flush-Ereignis in die Datenbank geschrieben. Ein Flush-Ereignis kann über EntityManager::flush() oder durch den Commit einer Transaktion ausgelöst werden.

Entity States

Entity-Objekte können einen der folgenden Status haben:

  • NEW: Eine neu erstelltes Entity-Objekt, das noch nie einem Persistence Context angehört hat.

  • MANAGED: Ein persistentes Entity-Objekt, für das ein ensprechender Eintrag in der Datenbank existiert oder ein neu erstelles Entity-Objekt, auf das die Persist-Operation angewendet wurde. Alle Änderungen an einem Entity-Objekt mit Status MANAGED werden beim nächsten Flush-Ereignis automatisch in die Datenbank übernommen (UPDATE-Statements werden abgesetzt).

  • DETACHED: Entity-Objekte, die nicht mehr vom Persistence Context verwaltet werden. Änderungen an solchen Entity-Objekten werden nicht mehr in die Datenbank übernommen. Das Entity-Objekt wird auch als DETACHED erkannt, wenn es aus einem anderen Persistence Context stammt (z. B. wenn es über einen anderen EntityManager geladen wurde).

  • REMOVED: Entity-Objekte, auf welche die Remove-Operation angewendet wurde.

Operationen

Mit den Operationen Persist, Merge, Remove und Detach kannst du den Status eines Entity-Objekts ändern.

entity-states.png
Entity States

Persist

Die Persist-Operation wird über EntityManager::persist() auf Entity-Objekte angewendet oder über Beziehungs-Eigenschaften mit CascadeType::PERSIST an sie weitergegeben. Die betreffenden Entity-Objekte bekommen anschliessend den Status MANAGED. Diese Operation macht nur für Entity-Objekte mit Status NEW Sinn. Diese Entity-Objekte werden beim nächsten Flush-Ereignis in die Datenbank geschrieben (INSERT-Statements werden abgesetzt).

Das Flush-Ereignis führt auch dazu, dass auf alle vom Persistence Context verwalteten Entity-Objekte mit Status MANAGED eine Persist-Operation angewendet wird. Auf Entity-Objekte mit Status MANAGED hat diese Operation zwar keinen Einfluss, sie wird aber dennoch an Beziehungs-Eigenschaften mit CascadeType::PERSIST weitergegeben. Das Anwenden dieser Operation auf Entity-Objekte mit Status DETACHED oder REMOVED führt zu einer 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-Operationen müssen innerhalb einer Transaktion ausgeführt werden.

Merge

Die Merge-Operation wird über EntityManager::merge() auf Entity-Objekte angewendet oder über Beziehungs-Eigenschaften mit CascadeType::MERGE an sie weitergegeben. Die Merge-Operation kopiert die Eigenschaften eines Entity-Objekts mit Status DETACHED auf die Eigenschaften eines Entity-Objekts mit Status MANAGED, das vom jeweiligen Persistence Context verwaltet wird. Wird vom Persistence Context zu diesem Zeitpunkt kein äquivalentes Entity-Objekt verwaltet, wird ein neues erstellt. EntityManager::merge() gibt das Entity-Objekt mit Status MANAGED zurück. Beziehungs-Eigenschaften mit CascadeType::MERGE werden mit diesem Entity-Objekt / diesen Entity-Objekten überschrieben.

Auf Entity-Objekte mit Status MANAGED hat diese Operation keinen Einfluss (EntityManager::merge() gibt z. B. die selbe Instanz zurück), sie wird aber dennoch an Beziehungs-Eigenschaften mit CascadeType::MERGE weitergegeben. Das Anwenden dieser Operationen auf Entity-Objekte mit Status DETACHED oder REMOVED führt zu einer Exception.

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

Merge-Operationen müssen innerhalb einer Transaktion ausgeführt werden.

Remove

Die Remove-Operation wird über EntityManager::remove() auf Entity-Objekte angewendet oder über Beziehungs-Eigenschaften mit CascadeType::MERGE/ aktiviertem Orphan Removal an sie weitergegeben. Die betreffenden Entity-Objekte bekommen anschliessend den Status REMOVED.

Diese Operation kann auf Entity-Objekte mit Status MANAGED angewendet werden. Diese werden beim nächsten Flush-Ereignis aus der Datenbank gelöscht (DELETE-Statements werden abgesetzt).

Auf Entity-Objekte mit Status REMOVED hat diese Operation keinen Einfluss, sie wird aber dennoch an Beziehungs-Eigenschaften mit CascadeType::REMOVE weitergegeben. Das Anwenden dieser Operationen auf Entity-Objekte mit Status NEW oder DETACHED führt zu einer Exception.

$em->remove($article);

Remove-Operationen müssen innerhalb einer Transaktion ausgeführt werden.

Refresh

Die Refresh-Operation wird über EntityManager::refresh() auf Entity-Objekte angewendet oder über Beziehungs-Eigenschaften mit CascadeType::REFRESH an sie weitergegeben. Dabei werden alle Eigenschaften mit den Werten der Datenbank aktualisiert. Änderungen werden dabei überschrieben.

Diese Operation kann auf Entity-Objekte mit Status MANAGED angewendet werden. Das Anwenden der Refresh-Operationen auf Entity-Objekte mit Status NEW, REMOVED oder DETACHED führt zu einer Exception.

$em->refresh($article);

Detach

Die Detach-Operation wird über EntityManager::detach() auf Entity-Objekte angewendet oder über Beziehungs-Eigenschaften mit CascadeType::DETACH an sie weitergegeben. Die betreffenden Entity-Objekte bekommen anschliessend den Status DETACHED.

Diese Operation kann auf Entity-Objekte mit Status MANAGED oder REMOVED angewendet werden. Dabei werden alle Änderungen verworfen, die noch nicht in die Datenbank geschrieben wurden (auch das Löschen von Entity-Objekten).

Auf Entity-Objekte mit Status NEW oder DETACHED hat diese Operation keinen Einfluss, sie wird aber dennoch an Beziehungs-Eigenschaften mit CascadeType::DETACH weitergegeben.

$em->detach($article);

EntityManager::clear() leert den ganzen Persistence Context, was dazu führt, dass alle von ihm verwalteten Entity-Objekte den Status DETACHED bekommen.

Callbacks

Du kannst Callback-Methoden für Lifecycle-Events direkt in der Entity implementieren. Diese Methoden sind magisch. Folgende Methoden stehen zur Verfügung:

Methode Beschreibung
_prePersist() Wird ausgeführt, bevor eine Persist-Operation auf das betreffende Entity-Objekt (nur wenn es den Status NEW hat) angewendet wird (z. B. EntityManager::persist()).
_postPersist() Wird nach einem Flush-Ereignis ausgeführt, wenn das betreffende Entity-Objekt neu persistiert wurde (nachdem die nötigen INSERT-Statements abgesetzt wurden).
_preRemove() Wird ausgeführt, bevor eine Remove-Operation auf das betreffende Entity-Objekt angewendet wird (z. B. EntityManager::remove()).
_postRemove() Wird nach einem Flush-Ereignis ausgeführt, wenn das betreffende Entity-Objekt aus der Datenbank gelöscht wurde (nachdem die nötigen DELETE-Statements abgesetzt wurden).
_preUpdate() Wird vor einem Flush-Ereignis ausgeführt, wenn beim betreffenden Entity-Objekt Änderungen festgestellt wurden (Änderungen an [Andreas, Link]transienten[/Andreas, Link] Eigenschaften, werden nicht erkannt). Nimmst du in dieser Methode Änderungen betreffenden Entity-Objekt vor, werden diese beim späteren Flush-Ereignis auch berücksichtigt.
_postUpdate() Wird nach einem Flush-Ereignis ausgeführt, wenn beim betreffenden Entity-Objekt Änderungen festgestellt wurden (nachdem die nötigen UPDATE-Statements abgesetzt wurden).
_postLoad() Wird ausgeführt, nachdem die betreffende Entity aus der Datenbank geladen wurde (betrifft auch die Refresh-Operation).


Callback-Methoden können eine beliebige Sichtbarkeit haben.

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 diesem Beispiel fügen wir der Entity Article eine neue Eigenschaft $lastMod vom Typ [Andreas, Link]DateTime[/Andreas, Link] hinzu. Die Eigenschaft soll das Datum und die Zeit der letzten Änderung enthalten. Um dies zu realisieren, nutzen wir die Callback-Methoden _prePersist() und _preUpdate(), um diese Eigenschaft zu aktualisieren.

Callback-Methoden kannst du auch in "Entity-Listener" auslagern. Diese annotierst du mit n2n\persistence\orm\annotation\AnnoEntityListeners.

class Article extends ObjectAdapter {
    private static function _annos(AnnoInit $ai) {
        $ai->c(new AnnoEntityListeners(LastModListener::getClass()));
    }
   
    private $lastMod;
   
    public function setLastMod(\DateTime $lastMod) {
        $this->lastMod = $lastMod;
    }
    /**
     * @return \DateTime
     */
    public function getLastMod() {
        return $this->lastMod;
    }
    
    // rest of class body
}

AnnoEntityListeners erwartet die ReflectionClass-Objekte der "Entity-Listener". Deshalb sollten auch deine "Entity-Listener" n2n\reflection\ObjectAdapter erweitern.

class LastModListener extends ObjectAdapter {
    private function _prePersist(Article $article) {
        $article->setLastMod(new \DateTime());
    }
    
    private function _preUpdate(Article $article) {
        $article->setLastMod(new \DateTime());
    }
}

Den magischen Callback-Methoden kannst du das betreffende Entity-Objekt übergeben lassen. "Entity-Listener"-Klassen werden magisch initialisiert (_init() ist verfügbar).

« Entity konfigurieren Transaktionen »

Kommentare

Du musst eingeloggt sein, damit du Beiträge erstellen kannst.

Fragen