Generic OR mapper
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.
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.
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.
The way of object and relation definition is described in the next two chapters:
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');
...
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.
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:
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.
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.
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.
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.
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.
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
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
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
}
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');
Saving objects is quite easy. Regardless you save objects or object trees, the
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).
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);
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.
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"
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.