Generischer OR-Mapper
Artikel bewerten:
Bitte bewerten Sie den Artikel per Klick auf einen der angezeigten Sterne:
Zurück zum Artikel!
1. Einleitung
In der objektorientierten Welt wird der Anspruch erhoben, Applikationen möglichst komplett
objektorientiert entwerfen und entwickeln zu können. Um dieser Forderung gerecht zu werden,
stößt jeder Entwickler unweigerlich auf das Problem, dass Daten in relationalen
Datenbanken gehalten werden (müssen). Steht kein Hilfsmittel zur Verfügung, muss der
DataMapper in jeder Applikation neu geschrieben werden. Das kostet nicht nur Zeit und Geld,
sondern ist gegen den Ansatz "don't repeat yourself", denn diese Vorgehensweise
produziert redundaten Quellcode.
Das APF-Modul genericormapper stellt eine Abstraktionsschicht zur Verfügung,
die dem Entwickler einen Großteil der Mapping-Arbeit abnimmt. Der Mapper übernimmt dabei
- Verwaltung von Objekten,
- Verwaltung von Beziehungen zwischen Objekten (Komposition und Assoziation) und
- CRUD-Funktionen auf Objekte und Objektstrukturen.
Für diese Aufgaben stehen eine Reihe von API-Funktionen zur Verfügung, die das Laden,
Manipulieren und Löschen von definierten Objekten in der Datenbank abbilden. Das
allgemeingültige Domänen-Objekt GenericDomainObject kann dabei entweder
direkt verwendet oder nochmals innerhalb der Datenschicht der Applikation in die Domänen-Objekte
der Anwendung übersetzt werden.
Die folgenden Kapitel zeigen, wie der OR-Mapper konfiguriert und eingesetzt werden kann. Das im
APF-Release enthaltene Modul usermanagement basiert auf dem OR-Mapper und kann als
weiterführendes Beispiel herangezogen werden. Das usermanagement-Modul wird unter
Mitgelieferte Module
näher beschrieben.
2. Konfiguration des OR-Mappers
2.1. Grundlagen
Um den OR-Mapper verwenden zu können, müssen zwei Konfigurationsdateien angelegt werden:
- {ENVIRONMENT}_{NAMEAFFIX}_objects.ini
- {ENVIRONMENT}_{NAMEAFFIX}_relations.ini
Dabei definiert die erste Datei die Objekte und deren Attribute, die zweite Konfigurationsdatei die
Beziehungen zwischen den Objekten aus der ersten. Da der GenericORRelationMapper den
connectionManager
zum Aufbau der Datenbankverbindung nutzt, muss gegebenenfalls noch eine Sektion in der
Datenbank-Verbindungskonfiguration angelegt werden.
Der Abschnitt {ENVIRONMENT} im Namen der beiden Konfigurationsdateien wird dabei
dem Registry-Wert Environment aus dem Namespace apf::core entnommen,
der Abschnitt {NAMEAFFIX} kann frei gewählt werden. Er dient als weiteres
Unterscheidungsmerkmal und ermöglicht, dass unterschiedliche Mapper-Konfigurationen pro
Applikation verwendet werden können. Letzeres ist vor allem dann interessant, wenn eine
Applikation mehrere Datenquellen bedienen möchte/muss.
2.2. Konfigurationsbeispiel
Ein Entwickler möchte ein Gästebuch entwickeln. Die
Quellcode-Dateien sind dabei im Namespace modules::myguestbook abgelegt und das
Gästebuch benötigt nur einen OR-Mapper. Weiterhin wurde der globale Registry-Wert
Environment nicht manipuliert, die aktuelle Anwendung wird im Context
sites::mysite ausgeführt und der Namenszusatz (NAMEAFFIX) lautet
guestbook. In diesem Fall tragen die beiden Konfigurationsdateien den Namen
DEFAULT_guestbook_objects.ini
sowie
DEFAULT_guestbook_relations.ini
und müssen im Ordner
/apps/config/modules/myguestbook/sites/mysite
abgelegt sein. Weitere Details zu Konfigurationsdateien, Namespaces und Kontext können im
Kapitel Konfiguration nachgelesen
werden.
2.3. Aufbau der Objekt- und Beziehungsdefinition
Die Syntax der Objekt- und Beziehungsdefinition gestaltet sich wie folgt:
2.3.1. Objektdefinition
Der GenericORRelationMapper stellt, wie bereits in der Einleitung angesprochen, ein
allgemeingültiges Domänen-Objekt zur Verfügung (GenericDomainObject),
das ein Objekt in der Datenhaltung repräsentiert. Der Typ des Objekts beschreibt sich dabei
nicht durch den Klassennamen, sondern durch das Attribut ObjectName der Klasse.
Die Definition der Objekte beinhaltet daher lediglich den Namen des Objekts (=Name der Sektion) und
die Attribute (=Properties der Klasse GenericDomainObject). Die folgende Codebox
zeigt den Aufbau einer typischen Objektdefinition:
[Application]
DisplayName = "VARCHAR(100)"
[User]
DisplayName = "VARCHAR(100)"
FirstName = "VARCHAR(100)"
LastName = "VARCHAR(100)"
EMail = "VARCHAR(100)"
Username = "VARCHAR(100)"
Password = "VARCHAR(100)"
[Group]
DisplayName = "VARCHAR(100)"
[Role]
DisplayName = "VARCHAR(100)"
Die Werte der Attribute bestimmen dabei die Auslegung der Felder in der Datenbank. Der Mapper kennt
dabei die allgemeingültigen Werte
- VARCHAR({LENGTH})
- TEXT
- DATE
die eigenständig in die entsprechenden SQL-Anweisungen "übersetzt" werden. Der Platzhalter
{LENGTH} kann dabei durch eine beliebige Zeichenkettenlänge ersetzt werden.
Alle darüber hinaus gehenden Feldtypen müssen ähnlich der Feldbeschreibung bei einer
CREATE TABLE-Anweisung formuliert werden. Mit den hier aufgeführten Werten lassen sich
jedoch die meisten Anwendungsfälle abbilden.
Die Attribute eines beliebigen Objekts können dann wie folgt adressiert werden:
...
$User = new GenericDomainObject('User');
$User->setProperty('FirstName','Christian');
$User->setProperty('LastName','Achatz');
...
echo 'Vorname: '.$User->getProperty('FirstName');
echo 'Name: '.$User->getProperty('LastName');
...
2.3.2. Beziehungsdefinition
Die Datei *_relations.ini definiert die Beziehungen zwischen den im vorherigen
Kapitel beschriebenen Objekten. Der Mapper kennt dabei zwei Arten von Beziehungen: Komposition und
Assoziation. Da Kompositionen im Gegensatz zu Assoziationen starke Bindungen sind, können Objekte,
die weitere Objekte komponieren, nicht gelöscht werden, da sonst den komponierten Objekten die
Existenzberechtigung entzogen werden würde. Dieser Fall wird vom Mapper deshalb mit einer
entsprechenden Meldung quittiert.
Hinweis:
Die Datenhaltungstheorie spricht bei der Auslegung der Beziehungen davon, dass jedes Objekt genau
einmal komponiert sein soll, da es in der Realität nur eine starke Zugehörigkeit eines
Objekts zu einem anderen geben kann. Weiterhin definiert eine Komposition eine Abhängigkeit
oder auch Existenzberechtigung eines Objekts. Bei der Definition der Beziehungen muss daher darauf
geachtet werden, dass abhängige Objekte entsprechend komponiert sind. Ein Gästebucheintrag
kann beispielsweise nicht ohne ein Gästebuch existieren, der Benutzer, dem der Eintrag zugeordnet
ist, dageben sehr wohl. In diesem Fall muss die Beziehung zwischen Gästebuch und
Gästebucheintrag von der Qualität "Komposition" sein, die Beziehung zwischen
Gästebucheintrag und dem Benutzer vom Typ "Assoziation".
Die folgende Codebox zeigt den Aufbau einer typischen Relationsdefinition:
[Application2Group]
Type = "COMPOSITION"
SourceObject = "Application"
TargetObject = "Group"
[Group2User]
Type = "ASSOCIATION"
SourceObject = "Group"
TargetObject = "User"
[Role2User]
Type = "ASSOCIATION"
SourceObject = "Role"
TargetObject = "User"
[Application2User]
Type = "COMPOSITION"
SourceObject = "Application"
TargetObject = "User"
[Application2Role]
Type = "COMPOSITION"
SourceObject = "Application"
TargetObject = "Role"
Der Sektionsname (z.B. Group2User) sollte sprechend gewält werden, da dieser
sowohl zum Laden von zu einem Objekt in Beziehung stehenden Objekten als auch für die
Inbeziehungssetzung beim Speichern von Objekten Verwendung findet. Der Typ beinhaltet die Qualität
der Beziehung, die Parameter SourceObject und TargetObject sind
eine Referenz auf die Sektion der Objektdefinition.
Hinweis:
Die Anzahl der Beziehungsdefinitionen ist nicht limitiert, die Definitionen sollten jedoch den
Anforderungen der Applikation gerecht werden. Hierbei gilt die Daumenregel, dass bei mehrmaliger und
gleichbedeutender Verwendung eines Attributs eines Objekts dieses in ein eigenes Objekt ausgelagert
und das jeweilige Objekt in Beziehung (Assoziation) zu diesem gesetz werden soll. Typisches Beispiel
ist die Sprache eines Objekts.
3. Setup der Datenbank
Nachdem die Konfigurationsdateien fertiggestellt sind, muss die Datenbank für die Verwendung
vorkonfiguriert werden. Dies kann manuell oder automatisiert vorgenommen passieren. Die manuelle
Variante kann unter
Manuelles Setup der Datenbank
nachgelesen werden.
Das folgende Skript zeigt, wie das Datenbank-Setup mit Hilfe des GenericORMapperSetup-Tools
das Layout der Tabellen automatisiert erstellt werden kann. Eine Vorlage für dieses Skript
befindet sich zudem im Ordner /apps/modules/genericormapper/data/tools des jeweiligen
adventure-codepack-* Releases und trägt den Namen setup.php. Dieses muss
gemäß den Bemerkungen unterhalb der Codebox für den entstprechenden Anwendungsfall
angepasst werden. Hier das Setup-Skript im Überblick:
// PageController einbinden
require('../../apps/core/pagecontroller/pagecontroller.php');
// Ggf. Werte der Registry anpassen
$Reg = &Singleton::getInstance('Registry');
$Reg->register('apf::core','Environment',{ENVIRONMENT});
// SetupMapper einbinden
import('modules::genericormapper::data::tools','GenericORMapperSetup');
// SetupMapper instanziieren
$SetupMapper = new GenericORMapperSetup();
// Context der Applikation bekannt geben (wichtig für die Konfigurationsdateien!)
$SetupMapper->set('Context',{CONTEXT});
// Ggf. MySQL Storage-Engine anpassen (Standard is MyISAM)
$SetupMapper->set('StorageEngine','...');
// Datenbanklayout erstellen
$SetupMapper->setupDatabase({CONFIG_NAMESPACE},{CONFIG_NAME_AFFIX},{CONNECTION_NAME});
// Datenbanklayout lediglich anzeigen
$SetupMapper->setupDatabase({CONFIG_NAMESPACE},{CONFIG_NAME_AFFIX});
Die eingesetzten Platzhalter haben folgende Bedeutung:
-
{ENVIRONMENT}: Umgebungsvariable der Applikation. Diese wird bei der Adressierung
von Konfigurationsdateien verwendet und muss auf den Wert gesetzt werden, der auch in der
Zielanwendung verwendet wird. Siehe hierzu Kapitel
Konfiguration.
-
{CONTEXT}: Context der Applikation. Dieser wird zur Addressierung der
Konfigurationsdateien verwendet und muss auf den Wert gesetzt werden, der auch in der
Zielanwendung verwendet wird. Siehe hierzu Kapitel
Konfiguration.
-
{CONFIG_NAMESPACE}: Namespace, unter dem die Konfigurationsdateien für den
OR-Mapper liegen (siehe Kapitel 2.2).
-
{CONFIG_NAME_AFFIX}: Namenszusatz der Konfigurationsdateien (siehe Kapitel 2.1).
-
{CONNECTION_NAME}: Name der Datenbankverbindung, die für das Setup genutzt
werden soll.
Weiterhin ist wichtig, dass die zu initialisierende Datenbank bereits existiert und der in der
Verbindungskonfiguration aufgeführte Benutzer CREATE TABLE-Rechte für diese
besitzt. Wird nach der Ausführung des Codes kein Fehler angezeigt, wurde das Setup erfolgreich
abgeschlossen. Das Ergebnis kann dann beispielsweise mit phpMyAdmin oder dem MySQLAdmin
überprüft werden.
Die Ausgabe des obigen Scripts sollte bei erfolgreicher Ausführung folgendes anzeigen:
CREATE TABLE IF NOT EXISTS `ent_application` (
`ApplicationID` TINYINT(5) NOT NULL auto_increment,
`DisplayName` VARCHAR(100) character set utf8 NOT NULL default '',
`CreationTimestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
`ModificationTimestamp` timestamp NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`ApplicationID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `ent_user` (
`UserID` TINYINT(5) NOT NULL auto_increment,
`DisplayName` VARCHAR(100) character set utf8 NOT NULL default '',
`FirstName` VARCHAR(100) character set utf8 NOT NULL default '',
`LastName` VARCHAR(100) character set utf8 NOT NULL default '',
`EMail` VARCHAR(100) character set utf8 NOT NULL default '',
`Username` VARCHAR(100) character set utf8 NOT NULL default '',
`Password` VARCHAR(100) character set utf8 NOT NULL default '',
`CreationTimestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
`ModificationTimestamp` timestamp NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`UserID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
...
CREATE TABLE IF NOT EXISTS `cmp_application2user` (
`CMPID` TINYINT(5) NOT NULL auto_increment,
`ApplicationID` TINYINT(5) NOT NULL default '0',
`UserID` TINYINT(5) NOT NULL default '0',
PRIMARY KEY (`CMPID`),
KEY `JOININDEX` (`ApplicationID`,`UserID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `cmp_application2role` (
`CMPID` TINYINT(5) NOT NULL auto_increment,
`ApplicationID` TINYINT(5) NOT NULL default '0',
`RoleID` TINYINT(5) NOT NULL default '0',
PRIMARY KEY (`CMPID`),
KEY `JOININDEX` (`ApplicationID`,`RoleID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
In phpMyAdmin sollte ungefähr folgende Ansicht erscheinen:
Damit ist die Konfiguration des Mappers abgeschlossen und dieser kann in der Anwendung verwendet
werden. Änderungen am Datenmodell können (noch) nicht automatisiert abgeglichen werden.
Hierzu müssen die Tabellen manuell angepasst werden. Da das Tabellendesign einigen wenigen
Grundregeln folgt, ist dieses jedoch einfach zu bewerkstelligen. Hilfestellungen können dem
Kapitel
Manuelles Setup der Datenbank
entnommen werden.
4. Verwendung des OR-Mappers
Der OR-Mapper, oder genauer die Komponente GenericORRelationMapper, bietet eine Reihe
von API-Methoden an, die zur Manipulation von Daten und Beziehungen eingesetzt werden können.
Hier ein Überblick über die Methoden, deren Parameter und Bedeutung:
-
loadObjectListByCriterion():
Läd eine Liste von Objekten an Hand eines Kriterien-Objekts.
-
loadObjectByCriterion():
Läd ein Objekt an Hand eines Kriterien-Objekts.
-
loadRelatedObjects():
Läd eine Liste von Objekten, die mit diesem über eine definierte Beziehung
verknüpft sind.
-
loadNotRelatedObjects():
Läd eine Liste von Objekten, die mit diesem über nicht über eine definierte
Beziehung verknüpft sind.
-
saveObject():
Speichert ein Objekt oder einen Objektbaum, der aus in Beziehung stehenden Domain-Objekten
besteht.
-
deleteObject():
Löscht ein Objekt. Dabei werden bestehende Assoziationen und Kompositionen aufgelöst.
-
createAssociation():
Erzeugt eine Assoziation zwischen zwei Objekten.
-
deleteAssociation():
Löscht die Assoziation zwischen zwei Objekten.
-
isAssociated():
Prüft, ob eine Assiziation zwischen zwei Objekten besteht.
-
loadObjectListByStatement():
Läd eine Liste von Objekten an Hand eines Statements.
-
loadObjectListByTextStatement():
Läd eine Liste von Objekten an Hand eines übergebenen SQL-Statements.
-
loadObjectListByIDs():
Läd eine Liste von Objekten an Hand eines übergebenen Arrays.
-
loadObjectByStatement():
Läd ein Objekt an Hand eines Statements.
-
loadObjectByTextStatement():
Läd eine Liste von Objekten an Hand eines übergebenen SQL-Statements.
-
loadObjectByID():
Läd ein Objekt an Hand einer übergebenen ID.
Die *Statement*-Methoden werden aus Performance-Gründen angeboten (siehe
Kapitel
Performance-Hacks).
Datails zu Argumenten und Rückgabewerten können der
API-Dokumentation der Module
entnommen werden, im Folgenden finden die wichtigsten Methoden jedoch Verwendung.
4.1. Erzeugen einer Instanz
Die Instanz eines OR-Mappers muss über die zugehörige Factory (GenericORMapperFactory)
erzeugt werden. Dies ist zum einen deshalb notwendig, um den konkreten OR-Mapper vor der Verwendung
zu initialisieren und zum anderen, damit mehrere OR-Mapper innerhalb einer Applikation verwendet
werden können. Letzteres ist in einfachen Anwendungen sicher nicht notwenig, in komplexeren
Konstrukten ist dies jedoch eine notwendige Anforderung.
Die folgende Codebox zeigt einen typischen Aufruf eines OR-Mappers:
// Factory erstellen
$ORMFactory = $this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory');
// Mapper von der Factory beziehen
$ORM = &$ORMFactory->getGenericORMapper(
{CONFIG_NAMESPACE},
{CONFIG_NAME_AFFIX},
{CONNECTION_NAME},
{SERVICE_OBJECT_TYPE}
);
Die Platzhalter haben dabei folgende Bedeutung:
-
{CONFIG_NAMESPACE}: Namespace, unter dem die Konfigurationsdateien für den
OR-Mapper liegen (siehe Kapitel 2.2).
-
{CONFIG_NAME_AFFIX}: Namenszusatz der Konfigurationsdateien (siehe Kapitel 2.1).
-
{CONNECTION_NAME}: Name der Datenbankverbindung, die für das Setup genutzt
werden soll.
-
{SERVICE_OBJECT_TYPE}: Art der Instanziierung des Mappers. Gültige Werte
sind "SINGLETON" und "SESSIONSINGLETON", Standard ist "SINGLETON".
Wichtig ist dabei weiterhin, dass die Factory mit der Methode __getServiceObject()
erzeugt wird, da es sonst zu unerwünschten Seiteneffekten hinsichtlich Konfiguration der Mapper
kommen kann.
4.2. Laden von Daten
Um die Beschreibung der Features plastischer gestalten zu können, soll folgendes UML als Basis
für Beispiele dienen. Das Diagramm enthält die Definition der Business-Objekte des
usermanagement-Moduls.
Die im Kapitel 4.2. verwendeten Code-Beispiele sind dabei dem genannten Modul entnommen.
4.2.1. Laden von Objekten
Für das Laden von Objekten stehen die Methoden
- loadObjectByCriterion()
- loadObjectByTextStatement()
- loadObjectByStatement()
- loadObjectByID()
zur Verfügung. Möchte der Entwickler auf einer Seite die Details eines Benutzers (siehe
UML-Diagramm) darstellen, so können die aufgeführten Methoden wie in der anschließend
dargestellten Codebox beschreiben eingesetzt werden:
// Fabric instanziieren
$ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory');
// Mapper mit Basis-Konfiguration laden
$ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt_1','umgt');
// Benutzer laden (1)
$Crit = new GenericCriterionObject();
$Crit->addPropertyIndicator('UserID',1);
$User = $ORM->loadObjectByCriterion('User',$Crit);
// Benutzer laden (2)
$select = 'SELECT * FROM ent_user WHERE UserID = \'1\';';
$User = $ORM->loadObjectByTextStatement('User',$select);
// Benutzer laden (3)
$User = $ORM->loadObjectByStatement('User','modules::usermanagement','load_user_by_id');
// Benutzer laden (4)
$User = $ORM->loadObjectByID('User',1);
Der Inhalt der Statement-Datei load_user_by_id ist dabei
SELECT * FROM ent_user WHERE UserID = '1';
Details zur Ausführung von Statement-Dateien können dem Kapitel
KlassenReferenz-MySQLHandler
entnommen werden.
4.2.2. Laden von Objekt-Listen
Für das Laden von Objekt-Listen stehen die Methoden
- loadObjectListByCriterion()
- loadObjectListByTextStatement()
- loadObjectListByStatement()
- loadObjectListByIDs()
zur Verfügung. Möchte der Entwickler auf einer Seite eine Liste von Benutzern (siehe
UML-Diagramm) darstellen, so können die aufgeführten Methoden wie in der anschließend
dargestellten Codebox beschreiben eingesetzt werden:
// Fabric instanziieren
$ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory');
// Mapper mit Basis-Konfiguration laden
$ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt_1','umgt');
// Benutzer-Liste laden (1)
$Crit = new GenericCriterionObject();
$Crit->addPropertyIndicator('DisplayName','a%');
$UserList = $ORM->loadObjectListByCriterion('User',$Crit);
// Benutzer-Liste laden (2)
$select = 'SELECT * FROM ent_user WHERE DisplayName LIKE \'a%\';';
$UserList = $ORM->loadObjectListByTextStatement('User',$select);
// Benutzer-Liste laden (3)
$UserList = $ORM->loadObjectListByStatement('User','modules::usermanagement','load_user_list');
// Benutzer-Liste laden (4)
$UserList = $ORM->loadObjectListByIDs('User',array(1,2,3,4,5,6));
Der Inhalt der Statement-Datei load_user_list ist dabei
SELECT * FROM ent_user WHERE DisplayName LIKE 'a%';
4.2.3. Nachladen von Beziehungsobjektlisten
Besteht die Notwendigkeit, bei der Auflistung der Benutzer, deren zugeordnete Gruppen mit
aufzuführen, können die Gruppen an Hand der Beziehung nachgeladen werden. Für das
Nachladen von zu einem Objekt in Beziehung stehenden Objekten kann die Methode
eingesetzt werden. Das folgende Beispiel zeigt, wie die einem Benutzer zugeordneten Gruppen
geladen werden können:
// Fabric instanziieren
$ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory');
// Mapper mit Basis-Konfiguration laden
$ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt_1','umgt');
// Benutzer-Liste laden
$Crit = new GenericCriterionObject();
$Crit->addOrderIndicator('DisplayName','ASC');
$UserList = $ORM->loadObjectListByCriterion('User',$Crit);
// Ausgeben der Liste inkl. Gruppen des Benutzers
for($i = 0; $ < count($UserList); $i++){
// Name des Benutzers ausgeben
echo '<br />'.$UserList[$i]->getProperty('DisplayName');
// Gruppen nachladen
$GroupList = $ORM->loadRelatedObjects($UserList[$i],'Group2User');
// Gruppen ausgeben
echo ' ,Gruppen: ';
for($j = 0; $j < count($GroupList); $j++){
echo $GroupList[$j]->getProperty('DisplayName').' ';
// end for
}
// Neue Zeile ausgeben
echo '<br />';
// end for
}
Zur Vereinfachung des Nachladens besitzt auch das Objekt GenericDomainObject die
Methode loadRelatedObjects(). Damit ist es möglich in der Präsentationsschicht,
und überall dort, wo keine Instanz des Mappers zur Verfügung steht, in Beziehung stehende
Objekte nachzuladen. Im obigen Beispiel können die einem Benutzer zugeordneten Gruppen damit auch
per
$GroupList = $UserList[$i]->loadRelatedObjects('Group2User');
geladen werden.
Hinweis: Die Menge der nachgeladenen Daten kann auch hier mit einem
GenericCriterionObject eingeschränkt werden. Die im Beispiel genannte
Gruppen-Liste kann wie folgt limitiert werden:
// Definieren der Limitierungsindikatoren
$Crit = new GenericCriterionObject();
$Crit->addOrderIndicator('DisplayName','ASC');
$Crit->addPropertyIndicator('DisplayName','A%');
$Crit->addCountIndicator(10);
// Laden der Liste ueber das Domänen-Objekt selbst
$GroupList = $UserList[$i]->loadRelatedObjects('Group2User',$Crit);
// Laden der Liste direkt ueber den OR-Mapper
$GroupList = $ORM->loadRelatedObjects($UserList[$i],'Group2User',$Crit);
4.2.4. Nachladen von "Nichtbeziehungsobjekten"
Oft besteht die Notwendigkeit, Objekte zu selektieren, die zu einem bestimmten Objekt (noch) nicht
in Beziehung stehen, für die jedoch eine Beziehung definiert ist. Ein konkreter Anwendungsfall
bezogen auf das oben gezeigte UML-Diagramm ist die Selektion aller Gruppen, zu denen ein Benutzer
noch keine Assoziation hat um diesen zur Gruppe hinzufügen zu können. Zu diesem Zweck
kann die Methode
eingesetzt werden. Das folgende Beispiel zeigt, wie alle Gruppen selektiert werden können, zu
denen der genannte Benutzer noch keine Beziehung besitzt:
// Fabric instanziieren
$ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory');
// Mapper mit Basis-Konfiguration laden
$ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt_1','umgt');
// Benutzer selektieren
$Crit = new GenericCriterionObject();
$Crit->addpropertyIndicator('DisplayName','Mustermann, Max');
$User = $ORM->loadObjectByCriterion('User',$Crit);
// Selektieren der nicht assoziierten Gruppen
$GroupList = $ORM->loadNotRelatedObjects($User,'Group2User');
// Ausgeben der Liste der noch nicht assoziierten Gruppen
for($i = 0; $ < count($GroupList); $i++){
echo '<br />'.$GroupList[$i]->getProperty('DisplayName');
// end for
}
Hinweis: Auch hier kann die Menge der nachgeladenen Objekte mit Hilfe des
GenericCriterionObject eingeschränkt werden. Häufiger Anwendungsfall ist
hier die Einschränkung über weitere Beziehungen der gewünschten Objekte zu anderen.
Im folgenden Beispiel sollen nur diejenigen Gruppen selektiert werden, zu denen der gewühlte
Benutzer noch keine Beziehung besitzt, die jedoch unterhalb eines definierten
Application-Objekts komponiert sind:
// Fabric instanziieren
$ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory');
// Mapper mit Basis-Konfiguration laden
$ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt_1','umgt');
// Benutzer selektieren
$Crit = new GenericCriterionObject();
$Crit->addpropertyIndicator('DisplayName','Mustermann, Max');
$User = $ORM->loadObjectByCriterion('User',$Crit);
// Additived Beziehungskriterium definieren
$Crit = new GenericCriterionObject();
$App = new GenericDomainObject('Application');
$App->setProperty('ApplicationID',1);
$Crit->addRelationIndicator('Application2Group',$App);
// Selektieren der nicht assoziierten Gruppen
$GroupList = $ORM->loadNotRelatedObjects($User,'Group2User',$Crit);
// Ausgeben der Liste der noch nicht assoziierten Gruppen
for($i = 0; $ < count($GroupList); $i++){
echo '<br />'.$GroupList[$i]->getProperty('DisplayName');
// end for
}
4.3. Speichern von Objekten
Für das Speichern von Objekten steht die Methode
zur Verfügung. Um einen Benutzer in der Datenbank zu speichern ist folgender Code notwendig:
// Fabric instanziieren
$ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory');
// Mapper mit Basis-Konfiguration laden
$ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt_1','umgt');
// Benutzer befuellen
$User = new GenericDomainObject('User');
$User->setProperty('FirstName','Christian');
$User->setProperty('LastName','Achatz');
// Benutzer speichern
$ORM->saveObject($User);
4.4. Speichern von Objekt-Bäumen
Wie bereits in der Einleitung angemerkt, kann der OR-Mapper nicht nur einzelne Objekte, sondern auch
Objektbäume speichern. Dieses Feature kann in der Datenschicht der Applikation insbesondere dazu
genutzt werden, um für die Applikation notwendige Beziehungen aufzubauen.
Aufgabenstellung: Beim Erstellen eines Benutzers, soll dieser unterhalb einer Applikation
komponiert werden. Diese Komposition kann später dazu genutzt werden um das Usermanagement
mandantenfähig zu gestalten.
Umsetzung: Um eine Beziehung zwischen einem Application- und einem
User-Objekt herzustellen und diese Beziehung auch zu speichern, kann die Methode
addRelatedObject() der Klasse GenericDomainObject verwendet werden.
Die folgende Codebox zeigt die Implementierung:
// Fabric instanziieren
$ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory');
// Mapper mit Basis-Konfiguration laden
$ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt_1','umgt');
// Applikation laden
$App = $ORM->loadObjectByID('Application',1);
// Benutzer befuellen
$User = new GenericDomainObject('User');
$User->setProperty('FirstName','Christian');
$User->setProperty('LastName','Achatz');
// Beziehung herstellen
$App->addRelatedObject('Application2User',$User);
// Objektbaum speichern
$ORM->saveObject($App);
Möchte der Entwickler im gleichen Zug dem Benutzer noch eine Gruppe und eine Rolle zuordnen,
muss der oben gezeigte Quellcode zwischen dem Befüllen des Benutzer-Objekts und der Herstellung
der Beziehung zum Application-Objekt entsprechend erweitert werden:
...
// Applikation laden
$App = $ORM->loadObjectByID('Application',1);
// Benutzer befuellen
$User = new GenericDomainObject('User');
$User->setProperty('FirstName','Christian');
$User->setProperty('LastName','Achatz');
// Gruppe laden
$Group = $ORM->loadObjectByID('Group',1);
// Rolle laden
$Role = $ORM->loadObjectByID('Role',1);
// Gruppe und Rolle zuweisen
$User->addRelatedObject('Group2User',$Group);
$User->addRelatedObject('Role2User',$Role);
// Beziehung herstellen
$App->addRelatedObject('Application2User',$User);
// Objektbaum speichern
$ORM->saveObject($App);
5. Übersicht zum GenericCriterionObject
Das vorliegende Kapitel möchte einen zusammenfassenden Überblick über die Nutzung des
GenericCriterionObject geben. Wie in den vorherigen Kapiteln angedeutet, kann das
Kriterium-Objekt dazu genutzt werden, Abfragen ohne Schreiben von SQL-Statements für den
Anwendungsfall zu konfigurieren. Das Objekt kann bei den load*ByCriterion()-Methoden
und beim Nachladen von in Beziehung stehenden Objekten und Objektlisten genutzt werden.
Die folgende Code-Box zeigt einen Überblick über die Einsatzmöglichkeiten des
GenericCriterionObjects am Beispiel einer Benutzer-Liste, deren Benutzer zu einer
Applikation gehören und eine definierte Gruppe zugeordnet haben:
class usermanagementManager extends coreObject
{
...
function getUserList(){
// Fabric instanziieren
$ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory');
// Mapper mit Basis-Konfiguration laden
$ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt_1','umgt');
// Erzeugen des Kriterien-Objekts
$Crit = new GenericCriterionObject();
// Hinzufuegen einer Beziehung zum Objekt "Application" (Komposition)
$Application = new GenericDomainObject('Application');
$Application->setProperty('ApplicationID',1);
$Crit->addRelationIndicator('Application2User',$Application);
// Hinzufuegen einer Beziehung zum Objekt "Group" (Assoziation)
$Group = new GenericDomainObject('Group');
$Group->setProperty('GroupID',1);
$Crit->addRelationIndicator('Group2User',$Group);
// Hinzufuegen einer Begrenzung der Anzahl mit definiertem Startpunkt
$Crit->addCountIndicator(0,3);
// Hinzufuegen einer Begrenzung der Anzahl
$Crit->addCountIndicator(2);
// Hinzufuegen einer Bedingung auf Ebene der Eigenschaften des zu ladenden Objekts
$Crit->addPropertyIndicator('LastName','Achatz');
// Hinzufuegen einer Sortierreihenfolge
$Crit->addOrderIndicator('FirstName','ASC');
$Crit->addOrderIndicator('LastName','DESC');
// Definition der zu ladenden Attribute eines Objekts
$Crit->addLoadedProperty('FirstName');
$Crit->addLoadedProperty('LastName');
// Laden einer Objektliste mit Hilfe des Kriterium-Objekts
return $ORM->loadObjectListByCriterion('User',$Crit);
// Laden eines Objekts mit Hilfe des Kriterium-Objekts
return $ORM->loadObjectByCriterion('User',$Crit);
// end function
}
...
// end class
}
Hinweise zum Quelltext:
-
Beziehungen:
Das Hinzufügen von Beziehungen zum Kriterien-Objekt beschreiben, dass das zu ladende Objekt
oder jedes Objekt der zu ladenden Liste in Beziehung zum Objekt des Kriteriums stehen muss. Wird
wie im Beispiel eine Beziehung zum Objekt Application (Komposition) und zum Objekt
Group (Assoziation) aufgebaut, ist das Ergebnis eine Liste von Objekten innerhalb einer
Applikation, die in einer bestimmten Gruppe sind.
Möchte der Entwickler alle Benutzer selektieren, die in einer Applikation enthalten sind,
einer definierten Gruppe angehören und eine bestimmte Rolle zugewiesen haben, müssen
drei Beziehungen gemäß der Beziehungskonfiguration zum Kriterium hinzugefügt
werden.
-
Sortierreihenfolge:
Die Reihenfolge der Aufrufe entscheidet die Sortierung. Soll die Sortierung in einer anderen
Reihenfolge vorgenommen werden, müssen die Sortierkriterien in der entsprechend anderen
Abfolge hinzugefügt werden. Der Wert ASC steht für aufsteigende
Sortierung, DESC für absteigende.
6. Erweiterung des Mapping- und Relation-Table
Wenn der GenericORRelationMapper über mehrere Anwendungen und mehrere Anwendungsfälle
hinweg eingesetzt wird, ergibt sich die Schwierigkeit, dass unterschiedliche Applikationen unterschiedliche
Bereiche der vom OR-Mapper verwalteten Datenbank nutzen. Hierzu kann entweder für den entsprechenden
Anwendungsfall jeweils eine passende Konfiguration angelegt werden oder der Entwickler definiert
eine für alle verwendbare Basis-Konfiguration (z.B. alle Objekte des Moduls usermanagement)
und nutzt die Methoden
- addMappingConfiguration()
- addRelationConfiguration()
um die allgemeingültige Konfiguration für den aktuellen Anwendungsfall zu erweitern. Mit
den genannten Funktionen können beliebige weitere Objektdefinitions- und Beziehungs-Konfigurationen
hinzugeladen werden. Das folgende Beispiel zeigt, wie die aufgeführten Methoden genutzt werden
können um den Wirkungsbereich des Mappers zu erweitern:
// Fabric instanziieren
$oRMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory');
// Mapper mit Basis-Konfiguration laden
$oRM = &$oRMF->getGenericORMapper('modules::usermanagement','umgt_1','umgt');
// Zusaetzliche Objekt-Definitionen hinzuladen
$oRM->addMappingConfiguration('modules::usermanagement','umgt_2');
// Zusaetzliche Beziehungs-Definitionen hinzuladen
$oRM->addRelationConfiguration('modules::usermanagement','umgt_2');
Die Syntax der Objektdefinitions- und Beziehungs-Konfigurationen ist dabei identisch zu den
Standard-Konfiguration, wie sie im Kapitel
2.3. Objekt- und-Beziehungsdefinition
diskutiert wurden. Die zusätzliche Objekt-Definition beinhaltete dabei die folgenden Objekte:
[Project]
DisplayName = "VARCHAR(100)"
Description = "TEXT"
[News]
DisplayName = "VARCHAR(100)"
Title = "VARCHAR(100)"
Content = "TEXT"
und die neu hinzugekommenen Beziehungen waren
[Application2Project]
Type = "COMPOSITION"
SourceObject = "Application"
TargetObject = "Project"
[Project2News]
Type = "COMPOSITION"
SourceObject = "Project"
TargetObject = "News"
7. Anmerkungen
Hinweise zur Performance können unter
Performance-Hacks
nachgelesen werden, das manuelle Setup der Datenbank ist unter
Manuelles Setup der Datenbank
erklärt. Die Quellcode-Dateien des
usermanagement-Moduls
können als weiterführende Beispiele herangezogen werden.
Kommentare
Möchten Sie den Artikel eine Anmerkung hinzufügen, oder haben Sie ergänzende Hinweise? Dann können Sie diese hier einfügen. Die bereits verfassten Anmerkungen und Kommentare finden Sie in der untenstehenden Liste.
«
1
»
Einträge/Seite: |
5 |
10 |
15 |
20 |
|
1
|
Christian
09.10.2008, 12:47:44
|
Der Thread http://forum.adventure-php-framework.org/de/viewtopic.php?f=5&t=54 im Forum beinhaltet auch einige interessante Punkte zum GenericORMapper.