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 'Surename: '.$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"Since version 1.14 you also can use self-references, which means that an object can reference another object of the same object type. There is no difference to normal references, you just configure the same name in source- and targetobject:
[User2BlockedUser]
Type = "ASSOCIATION"
SourceObject = "User"
TargetObject = "User"Also, when using this feature, you do not take care of anything, as long as the SQL-statements get generated from the methods of the OR mapper, and do not get written on your own.
Note:Since version 1.12 it is possible to define additional indices along with the objct definition. This may be necessary due to performance reasons and it is strongly recommended to use this feature, if you are heavily requesting data using index restriction.
In such cases an object definition can be added the AddIndices key. The information provided there is used during setup and update to create additional indices to speed up the query execution time.
The subsequent code box describes a simple example with three types of indices defined within a user's object definition:
[User]
...
FirstName = "VARCHAR(100)"
LastName = "VARCHAR(100)"
Username = "VARCHAR(100)"
Password = "VARCHAR(100)"
...
AddIndices = "FirstName,LastName(INDEX)|Username(UNIQUE)|Password(INDEX)"The additional index definition underlies the following scheme:
// 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}');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:
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)');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.
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 criterion.
The next code box shows a typical call of the mapper:
// create the factory using the desired service mode
$ormFact = &$this->getServiceObject(
'modules::genericormapper::data',
'GenericORMapperFactory'[,
{SERVICE_OBJECT_TYPE}]
);
// create the mapper using the factory
$orm = &$ormFact->getGenericORMapper(
{CONFIG_NAMESPACE},
{CONFIG_NAME_AFFIX},
{CONNECTION_NAME}[,
$logStatements = false]
);The place holders have the following meaning:
As of release 1.12 the factory service type also defines the service type of the mapper (=scope of the object concerning the service manager's instanciation model). In case, the GORM should be created only once per session due to performance reasons (mapping and relation tables are then only created once per visit) the factory creation statement must have the third param (service type) filled with the value SESSIONSINGLETON.
For development environments it is recommended to use the SINGLETON service type. Using the SESSIONSINGLETON type, the mapping and relation tables are only updated after the session has expired what may lead to unexpected results.
Further notes can be taken from the wiki page Typische Fehler beim GenericORMapper (German).
Please note, that the factory must be created by the getServiceObject() function to avoid unnecessary configuration side-effects.
As of release 1.12 the GORM can also be created using the DIServiceManager. This approach implies advantages to testability and decoupling of business layer and presentation layer.
Creating an instance of the GORM is thus directly done by the DIServiceManager and not using the factory described above. This is because the DIServiceManager only accepts explicit service objects for dynamic initialization of other services. For this reason, three new services are available that do not contain any logic but the necessary configuration information:
Details in the configuration beside an application sample can be found on the wiki page Erzeugen des GORM mit dem DIServiceManager (German).
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.
Please note, that the o/r mapper can only manage objects that are known by the current instance. As described in chapter 6 the mapping and relation configuration can be enhanced by adding further configuration files.
Additional hints can be taken from The object name "Application" does not exist ... (German).
To load objects, you can use the functions
If you intend to display the details of a user (see UML), you kan take the methods listed above to achieve this:
// 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:
SELECT * FROM ent_user WHERE UserID = '1';Details on the statement execution can be taken from the statement files documentation.
$user = $orm->loadObjectByID('User',1);
echo $user->getObjectId();$user = $orm->loadObjectByID('User',1);
echo $user->getObjectName();$user = $orm->loadObjectByID('User',1);
echo $user;// 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));SELECT * FROM ent_user WHERE DisplayName LIKE 'a%';// 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
}
// end for
}$GroupList = $UserList[$i]->loadRelatedObjects('Group2User');// 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);// 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
}// 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
}// 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');Beneath the amount of objects related to another object, release 1.12 introduces a method to request the amount of objects of a certain kind stored in the database. For this reason, the
method has been added. It takes the name of the object as defined in the mappin configuration as it's first parameter. As an optional second parameter, you can pass a GenericCriterionObject that limits the amount of objects using attribute indicators.
Requesting the amount of objects of type User and all users who's last name starts with character A can be requested as follows:
$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);// 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);// 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);Creating relations of type composition must be done exactly as described within the code block. This is because a composed object cannot live without it's father object. Associations can be added afterwards using createAssociation().
In case objects have to be created - as the user in the code snippet above - it is not necessary to save the user before creating it's composition with the application object. The GORM does this implicitly during saving the object tree (consisting of the Application, the User, and the relation between the two objects composing the user under the app).
// 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 generic O/R mapper is able to save object trees with various size. Please note, that it may be necessary to treat huge object trees a little bit different. But this is only necessary for applications with high performance requirements. In all other cases, the performance of the GORM is absolutely sufficient.
In case the amount of objects invoked during object tree persistance is greater than 20 including at least one relation per object, it is recommended to save the objects first by using saveObject(). Afterwards, the relations necessary can be created using createAssociation(). This kind of hanling is only possible for associations and cannot be applied for compositions. This is because of the quality of composition relations.
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:
class UsermanagementManager extends APFObject {
public 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);
}
}$criterion = new GenericCriterionObject();
$criterion
->addCountIndicator(1)
->addRelationIndicator('xxx', $sourceObject)
->addOrderIndicator('name')
->addPropertyIndicator($attributeName, $attributeValue);Since release 1.14 the GenericCriterionObject provides the possibility to use enhanced logical operators such as AND OR, XOR, and NOT. To support this feature, the facility to combine criterion objects has been introduced. You are now enabled to create any mixture of operators you need.
The definition of the logical operator can be defined by setLogicalOperator() method invoked on any criterion. By default, AND is used to guarantee backward compatibility. To define the logical operator, just add these two lines of code:
$criterion = new GenericCriterionObject();
$criterion->setLogicalOperator('OR');Please note, that the logical operator is not applied to the whole criterion instance but is stored to be used for the next call of addPropertyIndicator().
$criterion = new GenericCriterionObject();
$criterion->addPropertyIndicator('field1','value1')
->addPropertyIndicator('field2','value2')
->setLogicalOperator('OR')
->addPropertyIndicator('field3','value3')
->addPropertyIndicator('field4','value4');The above code block creates the following condition. Please note, that the table names and the aliases the GORM uses are left out for better readability:
[...] WHERE `field1`='value1' AND `field2`='value2' OR `field3`='value3' OR `field4`='value4'As you can see with this example, the operator is stored until it's next change.
Due to the fact, that the last condition evaluates to true even if only `field4` contains 'value4' adding conditions directly after changing the the operator may often not make sense. In case you want to build up the condition to evaluate to true only if the `field1` must contain 'value1' and at least one of the other fields contains the defined value, the second block must be noted in brackets. You can realize this as follows:
$criterion1 = new GenericCriterionObject();
$criterion2 = new GenericCriterionObject();
$criterion2->setLogicalOperator('OR')
->addPropertyIndicator('field2','value2')
->addPropertyIndicator('field3','value3')
->addPropertyIndicator('field4','value4');
$criterion1->addPropertyIndicator('field1','value1')
->addPropertyIndicator('field2+field3+field4',$criterion2);As you can get from the addPropertyIndicator() call this method takes a string or a criterion object as it's second argument. This kind of combining criterion objects is not limited. Using the above code to execute a query, the subsequent sql is generated:
[...] WHERE `field1`='value1' AND (`field2`='value2' OR `field3`='value3' OR `field4`='value4')$criterion1 = new GenericCriterionObject();
$criterion2 = new GenericCriterionObject();
$criterion2->setLogicalOperator('OR')
->addPropertyIndicator('field2','value2')
->addPropertyIndicator('field3','value3')
->addPropertyIndicator('field4','value4');
$criterion1->addPropertyIndicator('field1','value1')
->addPropertyIndicator('field2+field3+field4',$criterion2);
// changes to the GCO for 'field4'
$criterion2->addPropertyIndicator('field4','value4a');
$criterion1->addPropertyIndicator('field1','value1')
// overwriting the value for 'field2+field3+field4'
->addPropertyIndicator('field2+field3+field4',$criterion2);Since 1.14 the GenericCriterionObject supports configurable comparison operators. In the past, all comparisons have been done using the "=" sign. To be able to apply custom operators the addPropertyIndicator() has been added a third argument that takes the comparison operator.
$criterion = new GenericCriterionObject();
$criterion->addPropertyIndicator('field1',15,'<');The above sample shows the usage of the lesser than operator. As noted above, all types of operators can be used that are suitable for the database driver you use.
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
// 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');[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"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.