DEFAULT_guestbook_objects.iniDEFAULT_guestbook_relations.ini/apps/config/modules/myguestbook/sites/mysite[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)"bit(7) NOT NULL default b'0'...
$User = new GenericDomainObject('User');
$User->setProperty('FirstName','Christian');
$User->setProperty('LastName','Achatz');
...
echo 'Vorname: '.$User->getProperty('FirstName');
echo 'Name: '.$User->getProperty('LastName');
...[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"Mit dem Release 1.12 ist es möglich die Objekt-Definition mit zusätzlichen Indizes auszustatten. Dies kann aus Performance-Gründen notwendig sein und ist vor allem dann ratsam, wenn eine Property eines Objekts sehr häufig zur Abfrage von Daten genutzt wird.
In diesem Fall kann die die Objekt-Definition um den Schlüssel AddIndices erweitert werden. Diese Information wird vom automatischen Setup und Update dazu genutzt, weitere Indizes anzulegen um die Abfragen zu beschleunigen.
Die folgende Code-Box zeigt ein Beispiel für drei Indizes auf die wichtigsten Attribute eines Benutzer-Objekts:
[User]
...
FirstName = "VARCHAR(100)"
LastName = "VARCHAR(100)"
Username = "VARCHAR(100)"
Password = "VARCHAR(100)"
...
AddIndices = "FirstName,LastName(INDEX)|Username(UNIQUE)|Password(INDEX)"Die Definitionen unterliegen folgenden Regeln:
// include page controller
require('../../apps/core/pagecontroller/pagecontroller.php');
// configure the registry if desired
Registry::register('apf::core','Environment','{ENVIRONMENT}');
// include SetupMapper
import('modules::genericormapper::data::tools','GenericORMapperSetup');
// create setup tool
$setup = new GenericORMapperSetup();
// set Context (important for the configuration files!)
$setup->setContext('{CONTEXT}');
// adapt storage engine (default is MyISAM)
$setup->setStorageEngine('MyISAM|INNODB');
// adapt data type of the indexed columns, that are used for object and relation ids
//$setup->setIndexColumnDataType('INT(5) UNSIGNED');
// create database layout
$setup->setupDatabase('{CONFIG_NAMESPACE}','{CONFIG_NAME_AFFIX}','{CONNECTION_NAME}');
// display statements only
$setup->setupDatabase('{CONFIG_NAMESPACE}','{CONFIG_NAME_AFFIX}');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` INT(5) UNSIGNED 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` INT(5) UNSIGNED 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` INT(5) UNSIGNED NOT NULL auto_increment,
`ApplicationID` INT(5) UNSIGNED NOT NULL default '0',
`UserID` INT(5) UNSIGNED NOT NULL default '0',
PRIMARY KEY (`CMPID`),
KEY `JOININDEX` (`ApplicationID`,`UserID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `cmp_application2role` (
`CMPID` INT(5) UNSIGNED NOT NULL auto_increment,
`ApplicationID` INT(5) UNSIGNED NOT NULL default '0',
`RoleID` INT(5) UNSIGNED NOT NULL default '0',
PRIMARY KEY (`CMPID`),
KEY `JOININDEX` (`ApplicationID`,`RoleID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;$setup->setIndexColumnDataType('TINYINT(3)');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.
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.
Die Instanz eines O/R-Mappers muss über die zugehörige Factory (GenericORMapperFactory) erzeugt werden. Dies ist zum einen deshalb notwendig, um den konkreten O/R-Mapper vor der Verwendung zu initialisieren und zum anderen, damit mehrere O/R-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 O/R-Mappers:
// Factory im relevanten Service-Mode erstellen
$ormFact = &$this->__getServiceObject(
'modules::genericormapper::data',
'GenericORMapperFactory'[,
{SERVICE_OBJECT_TYPE}]
);
// Mapper von der Factory beziehen
$orm = &$ormFact->getGenericORMapper(
{CONFIG_NAMESPACE},
{CONFIG_NAME_AFFIX},
{CONNECTION_NAME}[,
$logStatements = false]
);Die Platzhalter haben dabei folgende Bedeutung:
Seit dem Release 1.12 definiert die Factory den Service-Typ des Mappers (=Gültigkeitsbereich des Objekts). Soll der GORM aus Performance-Gründen innerhalb einer Benutzer-Sitzung nur einmal erstellt werden - dies ist sinnvoll, da Mapping- und Beziehungs-Tabellen nur einmal initialisiert werden -, so muss bei der Erzeugung der Factory der dritte Paramater (Service-Typ) mit dem Wert SESSIONSINGLETON befüt werden.
Für Entwicklungs-Umgebungen empfiehlt es sich den Service-Typ auf den Wert SINGLETON einzustellen. Andernfalls werden Änderungen der Mapping- oder Beziehungs-Definitionen erst nach Ablauf der Session aktiv.
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.
Seit dem Release 1.12 kann der GORM auch mit dem DIServiceManager erzeugt werden. Diese Vorgehensweise hat deutliche Vorteile für die Testbarkeit einer Komponente und die Entkopplung der Business- von der Präsentations-Schicht.
Die Erzeugung des GORM-Service erfolgt dabei direkt über den DIServiceManager und nicht über die oben beschriebene Factory. Grund hierfür ist, dass der DIServiceManager nur explizite Services zur dynamischen Inititialisierung eines anderen Services akzeptiert. Dazu existieren drei Service-Implementierungen, die zwar keine Funktion tragen, jedoch die notwendige Konfigurations-Information in den GORM tragen:
Details zur Konfiguration und ein Anwendungsbeispiel finden sich im Wiki unter Erzeugen des GORM mit dem DIServiceManager.
Für das Laden von Objekten stehen die Methoden
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';$user = $orm->loadObjectByID('User',1);
echo $user->getObjectId();$user = $orm->loadObjectByID('User',1);
echo $user->getObjectName();$user = $orm->loadObjectByID('User',1);
echo $user;// 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));SELECT * FROM ent_user WHERE DisplayName LIKE 'a%';// 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; $i < count($UserList); $i++){
// Name des Benutzers ausgeben
echo $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').' ';
}
}$GroupList = $UserList[$i]->loadRelatedObjects('Group2User');// 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 O/R-Mapper
$GroupList = $ORM->loadRelatedObjects($UserList[$i],'Group2User',$Crit);// 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; $i < count($GroupList); $i++){
echo '
'.$GroupList[$i]->getProperty('DisplayName');
}// 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; $i < count($GroupList); $i++){
echo '
'.$GroupList[$i]->getProperty('DisplayName');
}// Fabric instanziieren
$ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory');
// Mapper mit Basis-Konfiguration laden
$ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt_1','umgt');
// Gruppe selektieren
$Group = $ORM->loadObjectByID('Group',1);
// Selektieren und Ausgeben der Anzahl der Benutzer einer Gruppe
echo $ORM->loadRelationMultiplicity($Group,'Group2User');Neben der Anzahl der zu einem Objekt in Beziehung stehenden Objekte können seit dem Release 1.12 auch die Anzahl der in der Datenbank befindlichen Objekte eines definierten Typs abgefragt werden. Hierzu steht die Methode
zur Verfügung. Als Parameter wird der Name des Objekt gemäß der Definition der Objekte in der Konfiguration erwartet. Optional kann noch ein GenericCriterionObject mitgegeben werden, das das Ergebnis auf Basis von Attributen des Objekts einschränken kann.
Die Abfragen aller Objekte vom Typ User und aller Benutzer mit dem Buchstaben A am Anfang des Nachnamens können wie folgt durchgeführt werden:
$ormf = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory');
$orm = &$ormf->getGenericORMapper('modules::usermanagement','umgt_1','umgt');
$totalUsers = $orm->loadObjectCount('User');
$crit = new GenericCriterionObject();
$crit->addPropertyIndicator('LastName','A%');
$usersWithA = $orm->loadObjectCount('User',$crit);// 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);// 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);Das Anlegen von Beziehungen des Typs Komposition muss exakt die im Quellcode vorgestellte Art der Beziehungs-Generierung verwendet werden. Dies ist der Fall, da ein komponiertes Objekt nicht ohne sein Vater-Objekt leben kann. Assoziationen können auch nachträglich per createAssociation() angelegt werden.
Sofern Objekte neu angelegt werden sollen - wie oben der Benutzer - ist es nicht notwendig, diesen vorher selbst zu speichern. Dies übernimmt der GORM implizit beim Speichern des kompletten Baumes (bestehend aus der Application, dem User und der Beziehung, die den Benutzer unter der Applikation komponiert).
// 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);Der GORM ist in der Lage, beliebig große Objekt-Bäume zu speichern. Hierbei gilt es jedoch zu beachten, dass sehr große Bäme aus Performance-Gründen etwas anders behandelt werden sollten. Dies ist jedoch nur Anwendungen notwendig, die sehr hohen Performance-Anforderungen unterliegen. In der Regel ist die Performance des GORM in der oben beschriebenen Vorgehensweise absolut ausreichend.
Sofern die Anzahl der bei einer Speicherung involvierten Objekte sehr groß wird (c.a. 20 Objekte mit jeweils mind. 1 Assoziations-Beziehung) ist zu empfehlen, die Objekte ohne das Aufbauen der Beziehungen mit einem Aufruf von saveObject() zu speichern und diese anschließend per createAssociation() anzulegen. Diese Art der Optimierung kann für Kompositionen nicht genutzt werden. Dies ist in der Bedeutung der Komposition begründet.
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 APFObject {
public 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 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);
}
}// 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');[Project]
DisplayName = "VARCHAR(100)"
Description = "TEXT"
[News]
DisplayName = "VARCHAR(100)"
Title = "VARCHAR(100)"
Content = "TEXT"[Application2Project]
Type = "COMPOSITION"
SourceObject = "Application"
TargetObject = "Project"
[Project2News]
Type = "COMPOSITION"
SourceObject = "Project"
TargetObject = "News"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.