Generic OR mapper - Custom domain objects

1. Introduction

In order to take a further step towards object orientation, the OR mapper since version 1.14 supports the optional usage of custom domain objects. With an additional configuration file you can define, which object types (or rather database tables) should be represented by custom domain objects (instead of the GenericDomainObject) and where the object definitions are located.
This enables the possibility of adding own, object specific functions to the domain object, which required an extra manager object before. This leads to better readable code and makes the usage in many cases noticeable easier.

With this feature we also deliver a GenericORMapperDomainObjectGenerator, which is able to generate a ready definition for your custom domain objects, using your configuration files, which then can be edited as you need it. The generated definition consists of a base-object, which provides a getter- and setter-method for each property, defined in the database, and which must extend the GenericDomainObject or any object which already extends it. The object used for extending can be defined in the configuration, so that, in the easiest case, no more changes are needed after correct configuration and usage of the GenericORMapperDomainObjectGenerator. Furthermore, the generated definition contains the "real" domain object, which extends the base object, and which is designed to be changed from you.
The base object must NOT be edited, because any change on it will be irrevocable removed when starting the generator (which also can be used as updater, because it always will generate the base objects new, which is important for any changes you make in the property configuration) again. For any changes you want to do, use the "real" domain object, because this will not be affected by the generator, since it already exists.

As another feature, the domain object can implement some "event-methods", which will be called from the OR mapper when performing specific actions when working with this object, for example before and after saving it. This, for instance could be used to have arrays/objects as an property of the object, converting them to better storable JSON before saving, and reload the arrays/objects after saving. Doing so, the application never needs to take care of converting the data to the correct format, the object can do it on it's own.
An overview of available event-methods can be found in the chapter Event methods.

The following chapters will show you how to configure, generate and use custom domain objects, and which events they support.

2. Configuration of custom domain objects

Whenever the OR mapper should use custom domain objects, create an additional configuration file next to your mapper configurations:

  • {ENVIRONMENT}_{NAMEAFFIX}_domainobjects.ini

The placeholders {ENVIRONMENT} and {NAMEAFFIX} can be adapted from the existing O/R mapper configurations.

In this file you need to define sections with the name of each object, which should be represented by a custom domain object, which is configured in your *_objects.ini. Objects, which do not need a custom domain object, don't need to be defined here, the GenericDomainObject will be automatically used for them.

Each section must define the following values:

  • Namespace: The namespace where the object definition is located or where it should be generated
  • Class: The class- and filename of the object

If a base object should extend another object than the GenericDomainObject, and the generator will be used, also the following, otherwise not needed values need to be set:

  • Base.Namespace: The namespace where the base-object definition is located
  • Base.Class: The class- and filename of the base-object which should be used

Important: Don't forget the fact, that your own base object must than extend the GenericDomainObject direct or indirect.

The following example is part of the configuration of the Postbox Extension, in which the custom domain objects were used first, and which can be used as secondary example for the usage of custom domain objects.

APF configuration
[Message] Namespace = "extensions::postbox::biz" Class = "Message" Base.Namespace = "extensions::postbox::biz" Base.Class = "AbstractMessage" [MessageChannel] Namespace = "extensions::postbox::biz" Class = "MessageChannel" Base.Namespace = "extensions::postbox::biz" Base.Class = "AbstractMessageChannel"
Here, 2 objects got defined, which both should extend a special base-object.

3. Generating custom domain objects

Due to the delivered GenericORMapperDomainObjectGenerator the creating of custom domain objects is really easy. After creating the configuration files, you just need to create and start a little script, which generates the objects:

PHP code
import('modules::genericormapper::data::tools','GenericORMapperDomainObjectGenerator'); $generator = new GenericORMapperDomainObjectGenerator(); $generator->setContext('{CONTEXT}'); // Set your context here $generator->generateServiceObjects('{NAMESPACE}', '{NAMEAFFIX}'); // set namespace and nameaffix of configuration files here
The placeholder {CONTEXT} needs to be replaced with your context, {NAMESPACE} with the namespace where your configurations are stored (without context) and {NAMEAFFIX} with the affix you defined in the filename (see above).

The GenericORMapperDomainObjectGenerator will create the definition of each configured custom domain object, at the location defined in the configuration. If the file is already existing, the generator will try to regenerate the base object within this file, in order to let API-changes, made in your *_objects.ini, take effect. For that reason the file contains specific, corresponding named comments, which must not be edited or deleted, just as little as the code between them, otherwise the loss of data could be the consequence! When updating, the "real" comain object itself will not be changed, any changes you made on it should not get deleted. All changes on the base object will irrevocable be lost.

Attention: We generally do not incur liability for any problem/data loss which was caused from the generation, although we do test it, we never can exclude problems with it. You should always copy the existing files before using the generator, in order to be able to restore them if any problem occurs!!

An example for the generated file (this one is taken from the message object defined above) could look like this:

PHP code
//<*MessageBase:start*> DO NOT CHANGE THIS COMMENT! /** * Automatically generated BaseObject for Message. !!DO NOT CHANGE THIS BASE-CLASS!! * CHANGES WILL BE OVERWRITTEN WHEN UPDATING!! * You can change class "Message" which will extend this base-class. */ import('extensions::postbox::biz', 'AbstractMessage'); class MessageBase extends AbstractMessage { public function __construct($objectName = null){ parent::__construct('Message'); } public function getText() { return $this->getProperty('Text'); } public function setText($value) { $this->setProperty('Text', $value); return $this; } public function getAuthorNameFallback() { return $this->getProperty('AuthorNameFallback'); } public function setAuthorNameFallback($value) { $this->setProperty('AuthorNameFallback', $value); return $this; } } // DO NOT CHANGE THIS COMMENT! <*MessageBase:end*> /** * @package extensions::postbox::biz * @class Message * * Domain object for "Message" * Use this class to add your own functions. */ class Message extends MessageBase { /** * Call parent's function because the objectName needs to be set. */ public function __construct($objectName = null){ parent::__construct(); } }
The base object MessageBase extends AbstractMessage which was especially defined in the configuration. Even the import of the needed file was done from the generator.
In *_objects.ini configuration of the OR mapper, the message object has got the properties "Text" and "AuthorNameFallback", corresponding to this, getter- and setter-methods were created for them. The setters always get a fluent-interface.
Finally you see the definition of the Message object, which extends the base object. This is the class, you now are allowed to edit for your own needs, for example you could add a "delete()" method for deleting this message.

If you had a closer look at the Postbox-extension, you maybe will have seen, that there the delete-method was already defined in AbstractMessage. As you see this is also possible, but normally you won't need it.

4. Usage of custom domain objects

If the objects were created following the instructions above, we can come to the usage now. In fact, you don't need to take care of anything special here. Due to the fact that the objects always need to extend the GenericDomainObject they can be used like every other GenericDomainObject and are therefore downward compatible. This is particularly practical, because the decision to use the custom domain objects does not need any change on existing code.

The OR mapper recognizes itself, due to the configuration, that it needs to create a custom domain object when loading data. That means, if you are loading any message (following the example above) from the database, you will now get a "Message"-object instead of a GenericDomainObject, which will have all your custom methods.
Whenever you want to generate a new object, you just import the corresponding file with an import(), and create an instance of the object and work with it as before:

PHP code
import('extensions::postbox::biz', 'Message'); $Message = new Message(); $Message->setText('ExampleText'); $Orm->saveObject($Message);

5. Event methods

As mentioned earlier, the OR mapper offers some "events". The domain objects can define an event-method for each event, which will be called on specific actions. At the moment we offer the following event methods, which get called from the OR mapper:

  • afterLoad(): Gets called after the object was created and filled with data from the database.
  • beforeSave(): Gets called before the object is being saved.
  • afterSave(): Gets called after the object has been saved to the database.

One possible example of using this events is the encoding end decoding of arrays or objects before/after saving or loading, which was mention in the Introduction.


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.