Daten verwalten
Im objektorientierten Ansatz von n2n verwalten wir Daten über Business Objects. In diesem Kapitel stellen wir dir die Arbeit mit Business Objects vor!
Business Objects erstellen
Entitäten sind Objekte, welche der Verwaltung von Daten dienen. Business Objects (in Deutsch Geschäftsobjekte) sind Entitäten, welche neben den Daten auch Logik zur Datenverarbeitung enthalten können. In unserem Blog Modul werden wir mit drei Business Objects arbeiten: BlogArticle
, BlogCategory
, BlogComment
. Bitte beachte, dass es nicht darum geht, ein tolles Blog Modul zu erstellen, sondern dir mit wenig Code möglichst viel Funktionalität von n2n zu demonstrieren.
Für unsere Business Objects legen wir in unserem Modul ein separates Verzeichnis an: /app/qs/bo
Business Objects bestehen aus ihren Eigenschaften und den entsprechenden Getter- und Setter Methoden. Das einzige, was etwas komplizierter zu verstehen ist, sind die Annotationen der Beziehungen zwischen den Business Objects.
Eigenschaften und Beziehungen
Werfe zuerst einen genaueren Blick auf die importierten Business Objects.
BlogArticle
Ein BlogArticle
kann mehrere Kommentare (OneToMany Beziehung) und mehrere Kategorien (ManyToMany) zugeteilt haben. Diese Beziehungen werden im Business Object annotiert:
// Annotation der Beziehungen: private static function _annos(AnnoInit $ai) { $ai->p('comments', new AnnoOneToMany(BlogComment::getClass(), 'blogArticle', CascadeType::ALL)); $ai->p('comments', new AnnoOrderBy(array('id' => 'DESC'))); $ai->p('categories', new AnnoManyToMany(BlogCategory::getClass(), null, CascadeType::PERSIST|CascadeType::MERGE)); }
Die verbundenen Kommentare und Kategorien stehen als Eigenschaften zur Verfügung ($comments
, $categories
). Bei Beziehungen werden die Eigenschaften (Property) annotiert, welche die verbundenen Objekte enthalten.
Bei den Kommentaren (BlogComments
) wird eine OneToMany Beziehung annotiert, weil auf einen BlogArticle
mehrere Kommentare entfallen können. AnnoOneToMany
werden folgende Parameter übergeben:
- Die Klasse, des verknüpften Objektes
- Die Eigenschaft des verknüpften Objektes, wo der Artikel gespeichert wird
- Der CascadeType legt fest, welche Operationen (
PERSIST
,MERGE
,REMOVE
,DETACH
) an die vernüpften Objekte weitergegeben werden. In unserem Falle werden sämtliche Operationen (CascadeType::ALL
) weitergeleitet. Dies bedeutet, dass wenn der Blog-Artikel gelöscht wird (REMOVE
), die Operation an die verbundenen Kommentare weitergegeben wird und diese ebenfalls gelöscht werden!
Die zweite Annotation von Comments legt fest, dass die Comments, wenn sie über getComments()
aufgerufen werden, immer mit id DESC
sortiert werden.
Die dritte Annotation legt die Beziehung zu BlogCategory
fest. Wir definieren eine Many-To-Many Beziehung. Hier lassen wir den zweiten Parameter (mappedBy
) leer und geben bei CascadeType nur PERSIST
und MERGE
weiter. Weil REMOVE
fehlt, werden beim Löschen eines Artikels, die verbundenen Kategorien nicht gelöscht. Das macht Sinn, weil ja auch noch andere Blog-Artikel mit diesen Kategorien verbunden sein könnten!
BlogCategory
Auch bei BlogCategory
sticht die Annotation der Beziehung zu BlogArticle
ins Auge:
private static function _annos(AnnoInit $ai) { $ai->p('articles', new AnnoManyToMany(BlogArticle::getClass(), 'categories', CascadeType::PERSIST|CascadeType::MERGE)); }
Hier sehen wir jetzt das andere Ende der Many-To-Many Beziehung zwischen Artikel und Kategorien. Im Unterschied zur Annotation bei BlogArticle
ist hier der zweite Parameter von AnnoManyToMany
nicht NULL
. Auf einer Seite einer Many-To-Many Beziehung müssen wir eine mappedBy
Eigenschaft angeben.
BlogComment
Bei BlogComment
sehen wir jetzt eine Many-To-One Beziehung. Es ist das andere Ende der One-To-Many Beziehung auf BlogArticle
:
private static function _annos(AnnoInit $ai) { $ai->p('blogArticle', new AnnoManyToOne(BlogArticle::getClass(), CascadeType::MERGE|CascadeType::PERSIST)); $ai->p('image', new AnnoManagedFile()); }
Die Annotation auf die Eigenschaft $image
zeigt an, dass es sich bei der Eigenschaft um ein File handelt. AnnoManagedFile() bewirkt, dass sich ein FileManager um die Speicherung des hochgeladenen Files kümmert.
Dass die Eigenschaft $image
etwas Spezielles ist, siehst du auch an der Setter Methode:
public function setImage(File $image = null) { $this->image = $image; }
Hier wird bestimmt, dass der übergebene Wert ein File
sein muss. Um auch NULL
Werte zu erlauben ist das = null
wichtig.
Eine weitere Besonderheit stellen wir im Konstruktor fest:
public function __construct() { $this->setCreated(new \DateTime()); }
Mit dieser Anweisung setzt du den Standardwert der Eigenschaft created
das aktuelle Datum und die entsprechende Zeit. Jeder Kommentar der erstellt wird, kriegt so das aktuelle Datum als created
Wert.
Die Annotationen der Beziehungen wollen wir als nächstes genauer anschauen.
Annotationen von Beziehungen
n2n kennt folgende Klassen, um Beziehungen zwischen Business Objects zu annotieren:
AnnoOneToMany
(1:n Beziehung)AnnoManyToOne
(n:1 Beziehung)AnnoManyToMany
(n:n Beziehung)AnnoOneToOne
(1:1 Beziehung)
Die ersten drei Klassen werden nachfolgend genauer erläutert.
1:n Beziehung
Diese Annotation befindet sich auf der Klasse BlogArticle
. Ein BlogArticle
kann mehrere BlogComments
haben. Die Klasse AnnoOneToMany
kennt folgende wichtige Parameter im Konstruktor:
Eigenschaft | Erklärung | Wert / Beispiel |
---|---|---|
$targetEntity |
Die Klasse des Objektes, zu welchem eine Beziehung besteht. | BlogComment::getClass() |
$mappedBy |
Die Eigenschaft auf der das andere Objekt die Beziehung speichert. | 'blogArticle' BlogComment besitzt die Eigenschaft $blogArticle , wo die Beziehung gespeichert wird. |
$cascadeType |
Hier wird definiert, welche Operationen weitergegeben werden. | CascadeType::ALL Bei allen Datenbankmanipulationen werden diese auf die angehängten Objekte übertragen. Achtung: dies schliesst das Löschen mit ein! |
$fetchType |
Hier wird bestimmt, wann die verbundenen Objekte geladen werden sollen. Unmittelbar beim Laden des Objektes oder erst beim Zugriff. | FetchType::EAGER , lädt alle verbundenen Objekte sofortFetchType::LAZY , lädt die Objekte erst, wenn sie gebraucht werden. |
$orphanRemoval |
Bestimmt, ob nicht mehr verlinkte Objekte gelöscht werden sollen. | true oder false |
CascadeType
$cascadeType
wollen wir uns genauer anschauen. Der CascadeType bestimmt, welche Operationen an die verbundenen Objekte weitergegeben werden. Es gibt folgende wichtige Typen:
CascadeType::PERSIST
(erstellen)CascadeType::MERGE
CascadeType::REMOVE
(löschen)CascadeType::ALL
Hier beachten wir PERSIST
und REMOVE
genauer. Wird ein Objekt persistiert, werden mit CascadeType::PERSIST
auch die verbundenen Objekte persistiert. Mit REMOVE
wird ein Objekt gelöscht. CascadeType::REMOVE
bedeutet entsprechend, dass auch alle verbundenen Objekte mitgelöscht werden! Bei CascadeType::ALL
werden alle Operationen weitergegeben.
CascadeType::PERSIST|CascadeType:Merge
oder CascadeType::ALL
sinnvoll. Ausschlaggebend ist meistens, ob REMOVE
weitergegeben werden darf oder nicht. Genauere Informationen findest du im Lifecycle Artikel.CascadeType::REMOVE
oder CascadeType::ALL
mitgegeben, werden verbundene Objekte mitgelöscht. Dies kann zu Datenverlust führen!n:1 Bezieung
Diese Annotation befindet sich auf BlogComment
. Ein BlogComment
gehört immer zu einem BlogArticle
. Die Klasse AnnoManyToOne
besitzt folgende wichtige Konstruktor Parameter:
Eigenschaft | Erklärung | Wert / Beispiel |
---|---|---|
$targetEntity |
Die Klasse des Objektes, zu welchem eine Beziehung besteht. | BlogArticle::getClass() |
$cascadeType |
Bestimmt welche Operationen an die verbundenen Objekte weitergegeben werden. | CascadeType::MERGE|CascadeType::PERSIST |
$fetchType |
Hier wird bestimmt, wann die verbundenen Objekte geladen werden sollen. |
Im Gegensatz zur 1:n Beziehung gibt es hier kein $orphanRemoval
.
n:n Beziehung
Die letzte Beziehung, welche wir genauer anschauen, ist die n:n Beziehung. Wir finden sie auf BlogArticle
und BlogCategory
. Ein BlogArticle
kann mehreren BlogCategory
zugeordnet sein und umgekehrt. Die Annotation AnnoManyToMany
besitzt folgende wichtige Konstruktor Parameter:
Eigenschaft | Erklärung | Wert / Beispiel |
---|---|---|
$targetEntity |
Die Klasse des Objektes, zu welchem eine Beziehung besteht. | BlogArticle::getClass() |
$mappedBy |
Der MappedBy Wert muss nur auf einer Seite der Beziehung angegeben werden. Das System entscheidet so, welche Klasse Master und welche Slave ist. | null oder die Eigenschaft auf dem anderen Objekt |
$cascadeType |
Bestimmt welche Operationen an die verbundenen Objekte weitergegeben werden. | CascadeType::MERGE|CascadeType::PERSIST |
$fetchType |
Hier wird bestimmt, wann die verbundenen Objekte geladen werden sollen. | |
$orphanRemoval |
Bestimmt, ob nicht mehr verlinkte Objekte gelöscht werden sollen. |
SQL-Dump
Damit sind die Business Objects, welche wir im Rahmen dieses Quickstarts brauchen, definiert und deren Beziehungen erklärt. Für die Speicherung der Business Objects braucht es noch die entsprechenden Tabellen in der Datenbank. n2n verwendet eine vordefinierte Naming-Strategy. Diese leitet aus den Namen von Klassen und Eigenschaften die korrespondierenden Tabellen- und Spaltennamen ab. Beispiel: BlogArticle
Objekte werden in der Tabelle blog_article
gespeichert.
AnnoTable
) und Spaltennamen (AnnoColumn
) individuell gewählt werden. n2n erlaubt gar die Definition einer eigenen Naming-Strategy.Datenbankbank Tabellen können auch über den Hangar erstellt werden. Im Quickstart erstellen wir die Tabellen aber mit dem folgenden SQL File.
Entities registrieren
Alle Entities müssen in n2n registriert werden. Ergänze dazu var/etc/qs/app.ini
um folgende Zeilen:
[orm] entities[] = "qs\bo\BlogArticle" entities[] = "qs\bo\BlogCategory" entities[] = "qs\bo\BlogComment"
Ab sofort kennt n2n unsere Business Objects. Jetzt sind wir bereit, um auf unsere Daten zuzugreifen!