Generic OR mapper

1. Introduction

Designing object oriented applications, you typically claim, that the complete design can be created on OO style - without break of media. In order to achieve this goal, each developer was yet faced with the problem of data storage in relation databases. If there is no tool, a DataMapper has to be implemented newly for each application. This does not only result in higher costs and lack of time, but is also not compatible with the DRY (don't repeat yourself) paradigma. You still are forced to produce redundant code.

The APF module genericormapper now provides an abstraction layer, that is intended to do most of the work a data mapper has to do. The functionality thereby can
  • manage objects,
  • manage relations between objects (compositions and assoziations) and
  • features CRUD functionality for objects and object structures (aka. trees).
For this tasks, the API features a couple of methods, that support loading, manipulation and deletion of objects within the database. The generic domain object (GenericDomainObject) returned by the API functions can on the one hand be used directly within your applications or your application features a simple data layer, that mapps the data mapper domain objects into your application's domain objects and vice versa.

The following chapters show how the OR mapper has to be configured and how you can use it. The module usermanagement shipped with the APF release is completely based on the GenericORMapper and thus can be used as an extended implementation example. Details on the module can be seen on the Usermanagement page.


2. Configuration of the OR mapper


2.1. Basics

To be able to user the OR mapper, two configuration files must be present:
  • {ENVIRONMENT}_{NAMEAFFIX}_objects.ini
  • {ENVIRONMENT}_{NAMEAFFIX}_relations.ini
The first file defines the objects and attributes, the second one is intended to define the relations between the objects from the first file. Due to the fact, that the GenericORRelationMapper uses the connectionManager for database access, a database connection configuration must be created if not done yet.

The {ENVIRONMENT} part of the configuration file names is taken from the registry directive Environment from the apf::core namespace. The {NAMEAFFIX} can be defined freely by the developer. This part of the name is inteded to provide the possibility of different mapper configurations for one context and one environment. This enables you to create applications, that are able to use multiple data sources.


2.2. Configuration examples

Imagine, that a developer wants to create a guestbook. The source code files are located in the modules::myguestbook namespace and the guestbook's data layer should us the OR mapper. Further, the registry value Environment is not touched (so it is still the default value) and the current application context is sites::mysite. Moreover, he decides, that the name affix should be identical to guestbook. In this case, the object configuration file must be named
Code
DEFAULT_guestbook_objects.ini
and the relation configuration file
Code
DEFAULT_guestbook_relations.ini
These must reside within the folder
Code
/apps/config/modules/myguestbook/sites/mysite
Further details on configuration, namespaces and contexts can be taken from the configuration section.


2.3. Object and relation definition

The way of object and relation definition is described in the next two chapters:

2.3.1. Object definition
The GenericORRelationMapper provides a generic domain object (GenericDomainObject) that represents a persistence object. The type of the concrete object is classified by the domain object's ObjectName property.

The object definition thus contains the name of the object (=name of the section) and the attributes (=properties of the GenericDomainObject class). The following codebox shows a typical object configuration:
APF-Konfiguration
[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)"
The values of the attributes represent the database data types of the object's properties. The mapper knows the following values and translates them into the right create table statements:
  • VARCHAR({LENGTH})
  • TEXT
  • DATE
The {LENGTH} place holder must be replaced by any number. All other values are directly used for the data type of a property. These must be valid data type definitions. Otherwise, the CREATE TABLE statements will fail. The values presented above should be suitable for most application cases.
Since release 1.11, the generic or mapper supports bit masking. For this reason, the value of the property definition must contain a valid BIT field definition like
Code
bit(7) NOT NULL default b'0'
Therby, it is not relevant, if the property is defined with a default value. But it is important that the declaration of the BIT field ocntains the keyword "BIT". Details can be taken from the forum discussion under Fehler mit BIT-Feldern (German).
The attributes of a domain object can then be addressed as follows:
PHP-Code
... $User = new GenericDomainObject('User'); $User->setProperty('FirstName','Christian'); $User->setProperty('LastName','Achatz'); ... echo 'Surename: '.$User->getProperty('FirstName'); echo 'Name: '.$User->getProperty('LastName'); ...

2.3.2. Relation definition
The file *_relations.ini defines the relations between the objects defined in the previous chapter. The mapper knows two types of relations: compositions and associations. Due to the fact, that compositions are strong relations, objects, that compose other objects, cannot be deleted due to data consistency. This case is checked by the mapper and it throws an error, if a object is tried to be deleted, that must not.

Note: The persistancy theory contains the rule, that each object should be composed exactly one time. This is because an object cannot have more than one strong binding to another object in real life, too. Futhermore, a composition relation defines the object's right to exist. Hence, be aware that your objects are composed concerning this rule. For example, a guestbook entry cannot exist without the guestbook object. Instead, a user can exist without the guestbook. This means, that the relation between a guestbook and an entry must be a composition, the relation of the entry to the user must be an association.

The following codebox shows a typical relation configuration:
APF-Konfiguration
[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"
The section name (e.g. Group2User) should be named self-explanatory, because this relation key can be used to load objects, that are related to others and to create a relation between two or more objects. The type attribute contains the quality of relation, the SourceObject and TargetObject params reference the relevant object definition sections of the object definition file.

Note:
  • The count of the relation definitions is not limited, each definition should be created to fit the requirements of the data model of the application. The rule of thumb here says, that when using one attribute more than one time in different objects, it should be outsourced to an independent object and referenced (associated) by the relevant objects. A typical example is the language.
  • If you have created an object tree using the method addRelatedObject(), you can read the related objects of the desired node (GenericDomainObject) within the tree using getRelatedObjects() for futher manipulation or processing.

3. Database setup

After the configuration files have been created, the database must be prepared for usage. This can be done manually or automatically. The manual way is described in the manual database setup chapter.

The following script shows, how the automated database setup can be done with aim of the GenericORMapperSetup tool. If you are searching for an example script, have a look at the /apps/modules/genericormapper/data/tools folder in the adventure-codepack-* release. The file is named setup.php and must be adapted to your application case:
PHP-Code
// include pagecontroller require('../../apps/core/pagecontroller/pagecontroller.php'); // adopt registry values, if desired $Reg = &Singleton::getInstance('Registry'); $Reg->register('apf::core','Environment',{ENVIRONMENT}); // include SetupMapper import('modules::genericormapper::data::tools','GenericORMapperSetup'); // create SetupMapper $SetupMapper = new GenericORMapperSetup(); // set Context of the applcication (important for addressing configuration files!) $SetupMapper->set('Context',{CONTEXT}); // adopt storage engine if desired (default is MyISAM) $SetupMapper->set('StorageEngine','...'); // create database layout $SetupMapper->setupDatabase({CONFIG_NAMESPACE},{CONFIG_NAME_AFFIX},{CONNECTION_NAME}); // display database layout only $SetupMapper->setupDatabase({CONFIG_NAMESPACE},{CONFIG_NAME_AFFIX});
The place holders within the script have the following meaning:
  • {ENVIRONMENT}: This is the environment indicator of the application. It is used for addressing configuration files and must be adopted, if your environment is set to a value different than the default one. For details have a look at the configuration chapter.

  • {CONTEXT}: This is the context of the application. It is used for addressing configuration files and must be adopted, if your environment is set to a value different than the default one. For details have a look at the configuration chapter.

  • {CONFIG_NAMESPACE}: The namespace, that contains the mapper configuration files (see chapter 2.2).

  • {CONFIG_NAME_AFFIX}: The name affix for the configuration files (see chapter 2.1).

  • {CONNECTION_NAME}: The name of the database connection, that is used for the setup and for productional use.

Further, it is important that the database, that should be initialized, must exist before setup is started. Additionally, the user connecting to the database must have CREATE TABLE rights. Otherwise, the setup could not be done. If no error is displayed during setup, the setup has finished successfully. The result can be checked by using phpMyAdmin or a MySQLAdmin tool.

Executing the script to display the create statements only, the output should look like this:
Code
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;
Using phpMyAdmin you will get the following screen:

PHPMYAdmin view
Now, the configuration of the mapper has finished and you can use it. Changes to the data model (at the moment) cannot be automatically applied to the database. For this reason, the affected tables must be altered manually. Due to the fact, that the database layout follows some easy ruls, you can easily do this yourself. Hints can be taken from the manual database setup chapter.
Supporting the task of updating a database setup with the generic OR mapper setup tool a update tool was introduced in release 1.11. For detailed description, please refer to Generic O/R mapper - database update.

4. Usage of the OR mapper

The OR mapper, more precisely the GenericORRelationMapper, offers a list of API methods, that can be used for data and relation usage and manipulation. The following list gives you an overview of the existing functions and their meaning:
  • loadObjectListByCriterion(): Loads an object list by a criterion object.

  • loadObjectByCriterion(): Loads an object by a criterion object.

  • loadRelatedObjects(): Loads a list of objects, that are related to a dedicated object, by a relation key.

  • loadNotRelatedObjects(): Loads a list of objects by a relation key, that are *not* related to a dedicated object.

  • loadRelationMultiplicity(): Returns the number of objects, that are related to a dedicated object.

  • saveObject(): Saves an object or object tree, that consists of objects related to each other using the defined relations.

  • deleteObject(): Deletes an object. Associations and compositions are deleted as well.

  • createAssociation(): Creates an association between two objects.

  • deleteAssociation(): Deletes the associations between two objects.

  • isAssociated(): Checks, if two objects are associated.

  • loadObjectListByStatement(): Loads a list of objects by a statement.

  • loadObjectListByTextStatement(): Loads a list of objects by a statement present as a string.

  • loadObjectListByIDs(): Loads a list of objects by a list of ids.

  • loadObjectByStatement(): Loads an object by a statement.

  • loadObjectByTextStatement(): Loads an object by a statement present as a string.

  • loadObjectByID(): Loads an object by a given id.

The *Statement* methods are provided due to performance optimization reasons. For details, please have a look at the performance hacks chapter. Details on the aruments can be taken from the API documentation page for the modules.


4.1. Creating an instance of the mapper

The instance of the OR mapper must be created using the GenericORMapperFactory. This is necessary, because the concrete mapper must be initialized prior to use and to have the possibility to use more than one instance per module. The latter one is probably not necessary within easy applications, but in complexe environments this may be a knock-out criteria.

The next code box shows a typical call of the mapper:
PHP-Code
// include the factory import('modules::genericormapper::data','GenericORMapperFactory'); // create the factory $ORMFactory = $this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory'); // create the mapper using the factory $ORM = &$ORMFactory->getGenericORMapper( {CONFIG_NAMESPACE}, {CONFIG_NAME_AFFIX}, {CONNECTION_NAME}, {SERVICE_OBJECT_TYPE}[, $logStatements = false] );
The place holders have the following meaning:
  • {CONFIG_NAMESPACE}: The namespace, the configuration files reside (see chapter 2.2).

  • {CONFIG_NAME_AFFIX}: The name affix, the configuration files have (see chapter 2.1).

  • {CONNECTION_NAME}: The name of the database connection used for setup and usage.

  • {SERVICE_OBJECT_TYPE}: The type of service object of the mapper. Allowed values are "SINGLETON" and "SESSIONSINGLETON". Default is "SINGLETON". Details can be taken from the service object section.

Please note, that the factory must be created by the __getServiceObject() function to avoid unnecessary configuration side-effects.

In case, you want to acivate statement logging for debugging reasons, the $logStatements must be set to true. Please do not use this option in productional environments! Details can be taken from the API documentation.


4.2. Loading data

To have a concrete example for the next code samples, the following UML diagram should be used. The picture contains the business object definition of the usermanagement module contained in the APF. The code presented here is thus taken from the module's code files.



4.2.1. Loading objects
To load objects, you can use the functions
  • loadObjectByCriterion()
  • loadObjectByTextStatement()
  • loadObjectByStatement()
  • loadObjectByID()
If you intend to display the details of a user (see UML), you kan take the methods listed above to achieve this:
PHP-Code
// create fabric $ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory'); // create mapper $ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt','umgt'); // load user (1) $Crit = new GenericCriterionObject(); $Crit->addPropertyIndicator('UserID',1); $User = $ORM->loadObjectByCriterion('User',$Crit); // load user (2) $select = 'SELECT * FROM ent_user WHERE UserID = \'1\';'; $User = $ORM->loadObjectByTextStatement('User',$select); // load user (3) $User = $ORM->loadObjectByStatement('User','modules::usermanagement','load_user_by_id'); // load user (4) $User = $ORM->loadObjectByID('User',1);
The content of the statement file load_user_by_id is:
Code
SELECT * FROM ent_user WHERE UserID = '1';
Details on the statement execution can be taken from the class reference table of the MySQLHandler.


4.2.2. Loading lists
For loading lists, the OR mapper features the following methods:
  • loadObjectListByCriterion()
  • loadObjectListByTextStatement()
  • loadObjectListByStatement()
  • loadObjectListByIDs()
If you like to display a list of users (see UML) you can use these methods as presented below:
PHP-Code
// create fabric $ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory'); // create mapper $ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt','umgt'); // load user list (1) $Crit = new GenericCriterionObject(); $Crit->addPropertyIndicator('DisplayName','a%'); $UserList = $ORM->loadObjectListByCriterion('User',$Crit); // load user list (2) $select = 'SELECT * FROM ent_user WHERE DisplayName LIKE \'a%\';'; $UserList = $ORM->loadObjectListByTextStatement('User',$select); // load user list (3) $UserList = $ORM->loadObjectListByStatement('User','modules::usermanagement','load_user_list'); // load user list (4) $UserList = $ORM->loadObjectListByIDs('User',array(1,2,3,4,5,6));
The content of the statement file load_user_list looks like this:
Code
SELECT * FROM ent_user WHERE DisplayName LIKE 'a%';

If you have to display the corresponding groups while listing the user details, you can use the
  • loadRelatedObjects()
function. This method returns the objects related to a dedicated object by the desired relation key. The next example shows, how to load the groups associated to a user:
PHP-Code
// create fabric $ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory'); // create mapper $ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt','umgt'); // load user list $Crit = new GenericCriterionObject(); $Crit->addOrderIndicator('DisplayName','ASC'); $UserList = $ORM->loadObjectListByCriterion('User',$Crit); // display the list including the associated groups for($i = 0; $ < count($UserList); $i++){ // display name of the user echo '
'.$UserList[$i]->getProperty('DisplayName'); // load corresponding groups $GroupList = $ORM->loadRelatedObjects($UserList[$i],'Group2User'); // display groups echo ' ,Gruppen: '; for($j = 0; $j < count($GroupList); $j++){ echo $GroupList[$j]->getProperty('DisplayName').' '; // end for } // enter new line echo '
'; // end for }
To ease the handling of loading related objects, the GenericDomainObject posesses the loadRelatedObjects() function. This enables you to load the related objects using a relation key, where no instance of the mapper is available. To apply this to the example above, the user's groups could be loaded using
PHP-Code
$GroupList = $UserList[$i]->loadRelatedObjects('Group2User');
Note: The amount of objects loaded, can be limited using the GenericCriterionObject. So if you like to load the first 10 groups only, include the following code in your application:
PHP-Code
// define the limit $Crit = new GenericCriterionObject(); $Crit->addOrderIndicator('DisplayName','ASC'); $Crit->addPropertyIndicator('DisplayName','A%'); $Crit->addCountIndicator(10); // load the group list using the domain object $GroupList = $UserList[$i]->loadRelatedObjects('Group2User',$Crit); // load the group list using the mapper $GroupList = $ORM->loadRelatedObjects($UserList[$i],'Group2User',$Crit);

Often you have to select objects, that are not (yet) related with a given object, but a relation is defined. A good example, concerning the UML diagram above, is the listing of groups, that a certain user has not been added yet. For this purpose, the
  • loadNotRelatedObjects()
function could be used. The next example shows, how groups not associated with desired user can be selected:
PHP-Code
// create fabric $ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory'); // create mapper $ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt','umgt'); // select user $Crit = new GenericCriterionObject(); $Crit->addpropertyIndicator('DisplayName','Mustermann, Max'); $User = $ORM->loadObjectByCriterion('User',$Crit); // select groups, that are not related with the user $GroupList = $ORM->loadNotRelatedObjects($User,'Group2User'); // present list for($i = 0; $ < count($GroupList); $i++){ echo '
'.$GroupList[$i]->getProperty('DisplayName'); // end for }
Note: The amount of objects loaded, can be limited using the GenericCriterionObject. A typical application case is to limit the list by relations to other objects. The following example explains, how to load all groups, that are not associated to the desired user but are composed under a Application object:
PHP-Code
// create fabric $ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory'); // create mapper $ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt','umgt'); // select user $Crit = new GenericCriterionObject(); $Crit->addpropertyIndicator('DisplayName','Mustermann, Max'); $User = $ORM->loadObjectByCriterion('User',$Crit); // define additive relation criterion $Crit = new GenericCriterionObject(); $App = new GenericDomainObject('Application'); $App->setProperty('ApplicationID',1); $Crit->addRelationIndicator('Application2Group',$App); // select groups, that are not related with the user $GroupList = $ORM->loadNotRelatedObjects($User,'Group2User',$Crit); // present list for($i = 0; $ < count($GroupList); $i++){ echo '
'.$GroupList[$i]->getProperty('DisplayName'); // end for }

4.2.5. Loading the relation multiplicity

To be able to find out, how much objects are related to a certain one, the
  • loadRelationMultiplicity()
method can be used. It returns the multiplicity by a given object and a relation key. In order to count the users within a group, add the follwing code to your business layer:
PHP-Code
// create fabric $ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory'); // create mapper $ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt','umgt'); // select group $Group = $ORM->loadObjectByID('Group',1); // select amount of users within the group echo $ORM->loadRelationMultiplicity($Group,'Group2User');

4.3. Saving objects

Saving objects is quite easy. Regardless you save objects or object trees, the
  • saveObject()
function can be used. The next code box shows how to save a user:
PHP-Code
// create fabric $ormf = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory'); // create mapper $orm = &$ORMF->getGenericORMapper('modules::usermanagement','umgt','umgt'); // set some attributes $user = new GenericDomainObject('User'); $user->setProperty('FirstName','Christian'); $user->setProperty('LastName','Achatz'); // save user $orm->saveObject($user);
As of release 1.11, the saved object (in this case $user) can directly be reused after calling saveObject(). The mapper therefor injects the current mapper instance and the id of the object within the database. Details on the feature request can be taken from the forum post Erweiterung GORM (Release 1.11) (German).

4.4. Saving object trees

As already mentioned above, the OR mapper is not only able to save flat object structures but also object trees. This feature can especially be used within your application's data layer to create the necessary relations.

Problem: When you create a User, it should be composed under the Application object. The latter one is used to ensure multi-client capability for the user management module.

Solution: To create the relation between these two objects (Application and User), the addRelatedObject() method of the GenericDomainObject can be used. The following code box shows, how the implementation is like:
PHP-Code
// create fabric $ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory'); // create mapper $ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt','umgt'); // load application object $App = $ORM->loadObjectByID('Application',1); // fill user $User = new GenericDomainObject('User'); $User->setProperty('FirstName','Christian'); $User->setProperty('LastName','Achatz'); // compose user $App->addRelatedObject('Application2User',$User); // save object tree $ORM->saveObject($App);
If you intend to add a group and a role at the same time, the php code above must be added the following lines:
PHP-Code
... // load application $App = $ORM->loadObjectByID('Application',1); // fill user $User = new GenericDomainObject('User'); $User->setProperty('FirstName','Christian'); $User->setProperty('LastName','Achatz'); // load group $Group = $ORM->loadObjectByID('Group',1); // load role $Role = $ORM->loadObjectByID('Role',1); // associate role and group $User->addRelatedObject('Group2User',$Group); $User->addRelatedObject('Role2User',$Role); // compose user $App->addRelatedObject('Application2User',$User); // save object tree $ORM->saveObject($App);

5. The GenericCriterionObject

The present chapter is intended to describe the usage of the GenericCriterionObject. As already mentioned in the chapters above, the class can be used to configure your request rather than to write SQL statements. The object can be used with all load*ByCriterion() methods and while loading relation objects on existing domain objects or domain object lists.

The code box below presents an application use case of the GenericCriterionObject, where a list of users should be loaded. The users should be assigned to a special group and must be composed under a certain application object:
PHP-Code
class UsermanagementManager extends coreObject { ... function getUserList(){ // create fabric $ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory'); // create mapper $ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt','umgt'); // create the criterion object $Crit = new GenericCriterionObject(); // add the relation definition for the composition to the "Application" object $Application = new GenericDomainObject('Application'); $Application->setProperty('ApplicationID',1); $Crit->addRelationIndicator('Application2User',$Application); // add the relation definition for the association to the "Group" object $Group = new GenericDomainObject('Group'); $Group->setProperty('GroupID',1); $Crit->addRelationIndicator('Group2User',$Group); // add a limit indicator $Crit->addCountIndicator(0,3); // add a property indicator applied to the objects to be loaded $Crit->addPropertyIndicator('LastName','Achatz'); // add an order indicator $Crit->addOrderIndicator('FirstName','ASC'); $Crit->addOrderIndicator('LastName','DESC'); // add a directive, which properties should be loaded $Crit->addLoadedProperty('FirstName'); $Crit->addLoadedProperty('LastName'); // load an object list with aid of the criterion object or ... return $ORM->loadObjectListByCriterion('User',$Crit); // ... load an object with aid of the criterion object return $ORM->loadObjectByCriterion('User',$Crit); } ... }
Notes on the source code:
  • Relations:
    Adding a relation indicator to the criterion object says, that the object or object list to be loaded must have a relation to the object within the criterion. If a relation to the Application object (composition) and to the Group object (association) is added, this means that the resulting object list belongs to a dedicated application and is assigned to a special group.
    If a developer is likely to select all users, that belong to a dedicated application and are are member of a group and are assigned a role, three relation indicators must be added to the criterion object.

  • Sort sequence:
    The order of calls decides the sort sequences. If you like to sort in another direction, the sort indicators must be changed. ASC stands for ascending and DESC for descending sortation.

6. Enhancement of mapping and relation table

If the GenericORRelationMapper should be used for several applications or multiple application cases, sometimes it is good to split the configuration into several pieces. This makes configuration more generic and the mapper can thus be enabled to use specific parts of a greater database. To do so, you have two different choices: you create one configuration set for each application case or you create only a basic configuration, that is interessting for all application cases (e.g. user management) and use the
  • addMappingConfiguration()
  • addRelationConfiguration()
to extend the configuration for the special use cas. With these functions, any object and relation configuration files can be added. The following example shows to you, how the functions can be used to upgrade the scope of the mapper:
PHP-Code
// create fabric $ORMF = &$this->__getServiceObject('modules::genericormapper::data','GenericORMapperFactory'); // create mapper $ORM = &$ORMF->getGenericORMapper('modules::usermanagement','umgt','umgt'); // add another object definition $oRM->addMappingConfiguration('modules::usermanagement','umgt_2'); // add another relation definition $oRM->addRelationConfiguration('modules::usermanagement','umgt_2');
The syntax of the object and relation configuration is the same as the default configuration described in the chapter 2.3. Object and relation definition. The additional object configuration file contains the following objects:
APF-Konfiguration
[Project] DisplayName = "VARCHAR(100)" Description = "TEXT" [News] DisplayName = "VARCHAR(100)" Title = "VARCHAR(100)" Content = "TEXT"
and the newly added relations are
APF-Konfiguration
[Application2Project] Type = "COMPOSITION" SourceObject = "Application" TargetObject = "Project" [Project2News] Type = "COMPOSITION" SourceObject = "Project" TargetObject = "News"

7. Notes

Hints on performance relevant issues can be found in the performance hacks chapter, the manual database setup is discussed in the manual database setup section. The source files of the usermanagement module can be used as an extended example.


Comments

Do you want to add a comment to the article above, or do you want to post additional hints? So please click here. Comments already posted can be found below.
There are no comments belonging to this article.