Adventure,PHP,Framework,PageController,FrontController,Pattern,Objektorientierung,OO,Software,Design,Wiederverwendbarkeit,UML,Tutorial,Benchmark,ausgezeichnete Performance

Suche:    
Downloads  |  SVN!  |  Roadmap  |  Forum!  |  Bugtracking  |  Gästebuch  |  Backlinks!  |  Referenzen!  |  Sitemap  |  Impressum  
 
Deutsch Adventure PHP Framework  Bookmark @ Technorati Bookmark @ del.icio.us Bookmark @ Mr. Wong Bookmark @ Simpy Bookmark @ Google Bookmark @ Digg.com Adventure PHP Framework Seite 063-Generischer-OR-Mapper drucken!

Generischer OR-Mapper (beta)

Artikel bewerten:
Dieser Artikel wurde von 1 Leser(n) mit
Adventure PHP Framework Artikel bewertenAdventure PHP Framework Artikel bewerten (2)
von 5 Punkten bewertet. Bewerten auch Sie diesen 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
  • loadRelatedObjects()
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&auml;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
  • loadNotRelatedObjects()
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äfiger 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
  • saveObject()
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.

Powered by WebRing.