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.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.
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 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 class reference table of the MySQLHandler.
$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);
}
}// 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.