Adventure,PHP,framework,page controller,front controller,pattern,object orientated design,software,development,reusability,uml,tutorial,benchmark,brilliant performance,

Search:    
Downloads  |  SVN!  |  Roadmap  |  Forum!  |  Bugtracking  |  Guestbook  |  Backlinks!  |  References!  |  Sitemap  |  Impress  
 
Deutsch | English Adventure PHP Framework  Bookmark @ Technorati Bookmark @ del.icio.us Bookmark @ Mr. Wong Bookmark @ Simpy Bookmark @ Google Bookmark @ Digg.com Adventure PHP Framework Print page 032-Comment-function
Comment function

Rank article:
This article has not yet been ranked. Vote this article first of all!

1. Introduction

This tutorial shows you how the comment function provided at the end of each page is designed and which framework components are used to build up this application. The tutorial also contains a narration concerning the design pattern mentioned on the manual pages. For this reason the text is seperated in three parts:
  • Description of the concept of the application,
  • illustration of modules aiming development and
  • source code explanation on the application's PHP files.
During the narrative chapters the design pattern mentioned on the basics page are illustrated by real live examples. In order to understand all the design rules in the contact form and guestbook tutorial, this chapters give a basic idea of design patterns in practice.



2. Design of the application

The comment function consists of two main parts: displaying output and a form. From a developer's point of view, the application contains output functionality, a form, a database table with the appropriate logic for reading the data from the database and writing data to it. Theory tells us a variety of design patterns that may be used to design the comment function. The following chapters perform a closer look at these pattern and tell some details about the usage and the benefit of them:


2.1. 3 tier architecture

The 3 tier architecture pattern assumes that deviding a program - in this case a part of a huge application - into database layer, business layer and presentation layer brings greater transparency. At first glance, this generates additional expenses that pays off during operation and further development. Another advantage is the fact, that the implemenations of each of the layers can be changed using another one without changing the code of the remaining layers. The tiers mentioned above are to fulfil the following tasks:
  • Database layer:
    This layer reads data from one ore more database tables or even one or more databases and presents them to the business layer. Vice versa the layer receives data and stores is in the database.
  • Business layer:
    The business stack controlles the workflow and behaviour of the software as well as the input and output of the application.
  • Presentation layer:
    The presentation layer takes care of the presentation of the application to the customer. In the case of web applications on of the major tasks is to build HTML-Code and handle the user interaction.
In the oriented object application design to some common phrases have been naturalize in the past, that should also be used here. In the context of a data layer pros merely speak of a mapper component (see 2.2) and in case of a presentation layer terms like view- or in this case document controller are familiar words to describe MVC style controllers. Each of this components is normally represented by a PHP class. Concerning the presetation layer this varies with the number of views contained in a presentation layer modul.


2.2. (OR) data mapper

A data mapper is a communication component between the "database world" and the "application world". This means that this piece of software deals with the database specifics and offers a common API to save and gather persistend data. More precisely, the mapper - a component belonging to the database layer - is used to read the data used in an application from a database and transform the data into application-readable format and vivce versa. The prefix OR indicates that the mapper mediates between a relational database that stores the data and a object oriented application. One common task of the OR mapper is to map a result set into a domain object known by the presend application. For this reason a mapper merely implements private mapping methods that are ofthe named map{ObjectName}2DomainObject(). Here {ObjectName} is to be replaced by the name of the desired domain object. The function takes one parameter - the database result array - and gives back an object of the type object {ObjectName}. Moreover, the mapper implements private and public methods for reading and writing objects.


2.3. Domain object pattern

The domain object pattern describes the fact that a single application commonly uses just a little amount of the whole data, that is stored to operate a huge application or portal. Big data management concepts merely contain a global scheme concept that is not needed by a single piece of the software. Assuming that the database installed to save the data of the present comment function is intended to store more data like meetings, cities, countries and the releations between these objects the comment function only needs a small section of the data stored there. It would be oversized that the comment function handles all the data provided there. For this reason each application only deals with the data interesting for the application's domain. Hence a single application only "knows" little amount of objects and is able to handle them with respect to the global data management concept. Domain objects are usually belonging to the business layer and have to be stored in the biz folder. Please note that these objects are familiar to all layers of an application: the data layer creates them out of the huge pool of data stored in the database and stores this extract back to the database, the business layer forms the application's workflow depending on the data and the presentation layer uses the domain objects to display data to the user.


2.4. MVC

The model view controller pattern is a software design pattern used in the presentation layer of the three tier architecture. It is only intended to improve the presentation layer design. The pattern describes the separation of data and application behaviour (model), appearance of the application (view) and the presentation layer functionality (controller). As already described in the basics section the software developer hopes to minimize maintenance and gains flexibility similar to the three tier architecture.

In case of the present comment function a common business component is used that consists of a manager and a domain object. These classes store the behaviour of the application (manager) as well as the data used (domain object). To give a more detailed structure to the pres folder two subfolders are created to store controller and view template files. This boosts clarity and makes it easy to differentiate the data stored in the pres folder.

Please keep in mind, that the MVC pattern itself needs further tools to support the developers with reasonable functions so that the programmer must not reinvent things in different applications one more time. Thus the MVC pattern is just one of the tools that are implemented in the adventure php framework to assist the programmer in building presentation layer components.


2.5. Page controller

The page controller component is together with the composite pattern a generic tool to create MVC style applications without developing things twice or more. Moreover, the page controller is a mechanism or convention how to integrate modules of the presentation layer. This fact is important for the present comment function because this module is integrated into the existing GUI by adding a special XML tag to the existing view template file of an article. In general, the page controller manages the building of the internal GUI DOM and the transformation into HTML code.



3. Software design

The current tutorial dispense with detailled UML style software design, because the comment function is not as complex as the guestbook software design. In addition the adventure php framework itself provides a modular structure and implements nearly all of the pattern described in chapter 2 so that it is not necessary to think out a more detailled software design.



4. Implementation of the software

There is no generic formula of how to implement software best. In many cases it is clever to take the top-down approach in other cases the bottom-up style is more efficient. Other people say that an application should be built up based on the functions - sort of vertically. The latter procedure can thus be split up in read-only functionality and write operations that are implemented one after the other. In this case the author prefers to choose the rapid prototyping approach.


4.1. Folder structure of the module

First of all the folder or namespace structure of the module must be created. Due to the fact, that the present software should be integratable in several applications it is called "module". As already mentioned in the basics section the program files should be stored in the /apps/modules folder. The module should be named comments and thus a folder /apps/modules/comments must be created. Now the folders that contain the program files of the different layers must be created in the module folder. These are: pres for presentation layer files, biz for classes belonging to the business layer and data for data layer components. Furthermore, the pres folder is split up in two sufolders called documentcontroller (hosting the controller files) and templates (for template files). In this case the following structure must be created:
/apps
     /config
     /core
     /modules
             /comments
                      /biz
                      /data
                      /pres
                           /documentcontroller
                           /templates
     /sites
     /tools
Since the business layer uses the pager component more folders must be created in the config folder later on.


4.2. Domain objekt

Our first class created is the domain object that is used within all layers. This class is called ArticleComment. Therefore the file ArticleComment.php must be created in the /apps/modules/comments/biz folder. The following code box shows the content of this class:

<?php
   
/**
   *  @package modules::comments::biz
   *  @class ArticleComment
   *
   *  Implementiert das Business-Objekt für das Modul Artikel-Kommentare.<br />
   *
   *  @author Christian W. Schäfer
   *  @version
   *  Version 0.1, 22.08.2007<br />
   *  Version 0.2, 03.09.2007 (Dokumentation ergänzt)<br />
   */
   
class ArticleComment extends coreObject
   
{

      
/**
      *  @private
      *  ID des Eintrags.
      */
      
var $__ID null;


      
/**
      *  @private
      *  Name des Autors.
      */
      
var $__Name;


      
/**
      *  @private
      *  E-Mail des Autors.
      */
      
var $__EMail;


      
/**
      *  @private
      *  Kommentar.
      */
      
var $__Comment;


      
/**
      *  @private
      *  Datum.
      */
      
var $__Date;


      
/**
      *  @private
      *  Uhrzeit.
      */
      
var $__Time;


      
/**
      *  @private
      *  Kategorie.
      */
      
var $__CategoryKey;


      function 
ArticleComment(){
      }

    
// end class
   
}
?>

Given the fact that the class inherits from coreObject no get() or set() methods must be created, because the coreObject already has this methods implemented. The get() and set() functions are used to fill the domain object with data and read this in other layers. In order to use this methods the member variables of the domain object class must follow the naming convention. This tells you to allways name the variables like $__{Name} where {Name} is the name of the variable that can be used to address it when using the abstract get() and set() functions. As an example the name of the given domain object can be filled and printed by
   $AC = new ArticleComment();
   
$AC->set('Name','Max Mustermann');
   echo 
$AC->get('Name'); 
The output in this case would be
  Max Mustermann

4.3. Data layer

The data layer consists of a data mapper class as mentioned in chapter 2 that contains reading functionality as a start. At this point it is to anticipate, that the pager component needs to have a mapper method that loads domain object by object ids. For this purpose we create the commentMapper class in the /apps/modules/comments/data folder. Please note that the class name must be the body of the filename. At first the file has to contain the proximate content:
   import('modules::comments::biz','ArticleComment');
   
import('core::database','MySQLHandler');


   class 
commentMapper extends coreObject
   
{

      function 
commentMapper(){
      }


      function 
loadArticleCommentByID($ArticleCommentID){

         
// fetch MySQLHandler
         
$SQL = &$this->__getServiceObject('core::database','MySQLHandler');

         
// Select entry
         
$select 'SELECT ArticleCommentID, Name, EMail, Comment, Date, Time
                    FROM article_comments
                    WHERE ArticleCommentID = \''
.$ArticleCommentID.'\';';
         
$result $SQL->executeTextStatement($select);

         
// return an domain object
         
return $this->__mapArticleComment2DomainObject($SQL->fetchData($result));

       
// end function
      
}


      function 
__mapArticleComment2DomainObject($ResultSet){

         
// create a new domain object
         
$ArticleComment = new ArticleComment();

         
// ArticleCommentID
         
if(isset($ResultSet['ArticleCommentID'])){
            
$ArticleComment->set('ID',$ResultSet['ArticleCommentID']);
          
// end if
         
}

         
// Name
         
if(isset($ResultSet['Name'])){
            
$ArticleComment->set('Name',$ResultSet['Name']);
          
// end if
         
}

         
// EMail
         
if(isset($ResultSet['EMail'])){
            
$ArticleComment->set('EMail',$ResultSet['EMail']);
          
// end if
         
}

         
// Comment
         
if(isset($ResultSet['Comment'])){
            
$ArticleComment->set('Comment',$ResultSet['Comment']);
          
// end if
         
}

         
// Date
         
if(isset($ResultSet['Date'])){
            
$ArticleComment->set('Date',$ResultSet['Date']);
          
// end if
         
}

         
// Time
         
if(isset($ResultSet['Time'])){
            
$ArticleComment->set('Time',$ResultSet['Time']);
          
// end if
         
}

         
// return filled domain object
         
return $ArticleComment;

       
// end function
      
}

    
// end class
   
Besides, the source code has the following meaning:
  • The two import() calls import the classes necessary. Among these are the domain object ArticleComment and the MySQL abstraction component MySQLHandler.
  • The method loadArticleCommentByID() loads a comment object by a given database id - the primary key of the table article_comments. In this case there is no configuration created to map the field names of the table, becaus this would be oversized to the current application. This eases implementation and gains the speed of the data layer component. Within the loadArticleCommentByID() function the MySQLHandler is used to execute a SQL statement and to fetch the database result. After fetching the result from the database the result array is mapped into a domain object by use of the private method __mapArticleComment2DomainObject() and given back to the calling function.
  • __mapArticleComment2DomainObject() is a mapping method as described in chapter 2, that translates an relational database result array into a domain object known by the application.
To generate the database table necessary for this application the init script init_comments.sql stored in the folder /apps/modules/comments/data/scripts must be executed. To raise clearness it is recommended to store the scripts that inialize the database for beeing used by applications created by yourself in the /apps/modules/{ModuleName}/data/scripts folder. Furthermore this eases the allocation of an applications' tables.


4.4. Business layer

The central business class is a manager, that coordinates the software workflow. For this reason we create a file named commentManager.php in the folder /apps/modules/comments/biz. In case of the read-only access this class only has to get data from the data layer and make it available to presentation layer. As mentioned several times the business class makes use of the pager component shipped with the codepack release files. Hence we have to configure the pager for use with the commentManager.php later on. The subsequent printed code box shows the sceleton of the class:
   import('modules::pager::biz','pagerManager');
   
import('modules::comments::data','commentMapper');
   
import('modules::pager::biz','pagerManager');
   
import('tools::link','frontcontrollerLinkHandler');


   class 
commentManager extends coreObject
   
{

      var 
$__CategoryKey;


      function 
commentManager(){
      }


      function 
init($CategoryKey){
         
$this->__CategoryKey $CategoryKey;
       
// end function
      
}


      function 
loadEntries(){
      }

    
// end class
   
Besides, the source code has the following meaning:
  • The two import() calls import the classes necessary. Among these are the domain object ArticleComment, the data layer component commentMapper, the pagerManager and the frontcontrollerLinkHandler. The latter is used to generate the link to that the user is forwared after saving the entry.
  • The class member $__CategoryKey stores the category key, the specific entry is associated with. This key is also used during loading the comments.
  • To use the business component with the method getAndInitServiceObject() a init() methode must be implemented to initialize the class with the desired parameter set. Here, the class should be initialized with the current category key.
  • loadEntries() is the prototype of the method to load the entries of a single category to give the list back to the presentation layer.
Let's have a closer look at the function described at the end of the list. The pagerManager component features the following functions concerning the API documentation:
  • setAnchorName() (set the name of the anchor)
  • loadEntries() (load the IDs of the desired entries)
  • getPager() (generate the HTML output of the pager)
  • getPagerURLParameters() (load the URL parameters used by the pager manager)
To use the pager manager a reference on a specific pagerManagery must be obtained by use of the pagerManagerFabric. While getting a pager manager the fabric must be told the configuration section to use to initialize the pager manager. The PHP code therefore looks as follows:
   // get a singleton instance of the pager manager fabric
   
$pMF = &$this->__getServiceObject('modules::pager::biz','pagerManagerFabric');

   
// get an instance of the pagerManager
   
$pM = &$pMF->getPagerManager('ArticleComments',array('CategoryKey' => $this->__CategoryKey)); 
With this code a pager manager is created and initialized with the configuration keys located in the configuration section ArticleComments. Moreover, the pager manager is initialized with the CategoryKey contained in the local member variable $__CategoryKey of the commentManager. The second argument of the getPagerManager() initializes the SQL parameter of the statements to get the count of the entries within one category and load the ids of the desired category. To configure these statements with further parameters, the second argument of the getPagerManager() function has to be used.

The configuration of the pager manager is located in the namespace modules::pager and the context path of the current application. In case of this documentation site the context is sites::demosite. This results in the path /apps/config/modules/pager/sites/demosite. There, a counfiguration file with the name DEFAULT_pager.ini must exist and contain the following content:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ArticleComments                                                                                  ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[ArticleComments]
; number of entries per page
Pager.EntriesPerPage = "5"

; names of the URL parameters for indicating start and number of entries per page
Pager.ParameterStartName = "PgrStr"
Pager.ParameterCountName = "PgrAnz"

; namespace and name of the statements to select a count and the ids
Pager.StatementNamespace = "modules::comments"
Pager.CountStatement = "load_entries_count"
Pager.CountStatement.Params = "CategoryKey:standard"
Pager.EntriesStatement = "load_entry_ids"
Pager.EntriesStatement.Params = "CategoryKey:standard"

; namespace and template for displaying the HTML output of the pager
Pager.DesignNamespace = "modules::pager::pres::templates"
Pager.DesignTemplate = "pager_2"
This set of parameters fully configures the pager component. Most of these keys could be applied for every use case. What is different in each application are the SQL statements to load the number of entries or the ids of the favored entries. These two statements are indicated through theire namespace and theire names. A namespace is a folder path seperated by "::" instead of "/" not containing the context subpath and the name is the name of the file without the environment prefix and the file's extension (.sql for statement files). The context is given to the pager manager during creating the instance in the pager manager fabric. The statement files must be located in a subfolder of the module's configuration, because statement are some kind of configuration. The statement files must therefore be stored in the folder /apps/config/modules/comments/sites/demosite/statements. The statements folder is expected by the MySQLHandler. As already mentioned in the configuration chapter the file name must be prefixed with the environment variable's content and be suffixed by the extension .sql. In the case of the comment function the two files mzst be named
  • DEFAULT_load_entries_count.sql
  • DEFAULT_load_entry_ids.sql
Furthermore the pagerManager expects, that the result set and stamement variables are named corresponding to the naming convention. As an example, the entries count statement must look like this:
SELECT COUNT(*) AS EntriesCount
FROM article_comments
WHERE CategoryKey = '[CategoryKey]'
GROUP BY ArticleCommentID;
Here, the pager manager expects that the resultset of the statement contains the array offset EntriesCount. For this reason the selected row or value must be aliased. In order to allow further configuration parameters, these must be included using the "[" and "]" directives. The content of the load_entry_ids statement must look like this:
SELECT ArticleCommentID AS DB_ID
FROM article_comments
WHERE CategoryKey = '[CategoryKey]'
ORDER BY Date DESC, Time DESC
LIMIT [Start],[EntriesCount];
Within this statement, the pagerManager expects the result set to contain an offset with the name DB_ID - so another alias is necessary. Parameters like [CategoryKey] can be used as described above. Another characteristic of the pager is the LIMIT directive. This clause must contain the parameter
  • [Start]
  • [EntriesCount]
to be filled with the dynamic pager numbers to be able to display the desired page. The rest of the statement can be as complex as the application needs it to be.

After having learned how to configure the pager, the subsequently printed code box shows the usage of the component. The loadEntries() method of the commentManager looks as follows:
   function loadEntries(){

      
// get pagerManager
      
$pMF = &$this->__getServiceObject('modules::pager::biz','pagerManagerFabric');
      
$pM = &$pMF->getPagerManager('ArticleComments',array('CategoryKey' => $this->__CategoryKey));

      
// load comments
      
$M = &$this->__getServiceObject('modules::comments::data','commentMapper');
      return 
$pM->loadEntriesByAppDataComponent($M,'loadArticleCommentByID');

    
// end function
   
As a first step, the instance of the pager manager is fetched using the PagerManagerFabric, further the instance of the data layer component (the mapper) is instanciated. In the third step the mapper is used to load the desired domain objects with help of the pager manager. Another possibility of loading the domain objects to display on the current page is to manually load the domain objects using a for loop. The corresponding PHP code looks as follows:
   function loadEntries(){

      
// get pagerManager
      
$pMF = &$this->__getServiceObject('modules::pager::biz','pagerManagerFabric');
      
$pM = &$pMF->getPagerManager('ArticleComments',array('CategoryKey' => $this->__CategoryKey));

      
// load IDs
      
$EntryIDs $pM->loadEntries();

      
// load comments
      
$M = &$this->__getServiceObject('modules::comments::data','commentMapper');

      
$Entries = array();

      for(
$i 0$i count($EntryIDs); $i++){
         
$Entries[] = $M->loadArticleCommentByID($EntryIDs[$i]);
       
// end for
      
}

      
// return list
      
return $Entries;

    
// end function
   
Both possibilities are given by the pager manager, but the latter one is intended to be able to sort the entries after loading within the business layer or do something different before giving them to the presentation layer component. The comment function uses the first alternative, the next tutorials make use of the second one. With this step the implementation of the business layer is finished.


4.5. Presentation layer

The display of the comments in a pagable list contains only one view beneath the view that includes the module - the listing. Let's at first have a look at the inclusion of the module, due to the fact, that it brings a little difference compared to usual view definitions. As described above it should be possible to integrate the comment function in an existing view template file of content file. In soing so, the <core:importdesign /> tag can be used. This tag takes - according to standard taglibs - the XML parameters namespace to declare the namespace of the templates, template for the definition of the template name and the optional parameter incparam to specify the URL parameter, that should be used to manage the view content. The latter is important in this case. By default this parameter is filled with the value "pagepart". This fact rescues conflict potential, because the parameter could be already used in other applications to control the contents of a view. Further, the template developer should be able to decide which comments are displayed. Therefore we introduce the XML parameter categorykey, that defines the category of comments. This mechanism was silently introduced in the chapters above with the knowledge, that the comments must be distinguishable. By means of the database this differentiation is feasible by other possibilities. But this example should be kept easy to understand. More complex database designs can be seen in the guestbook tutorial.

The inclusion of the module within an existing template can be done by
  <core:importdesign
      namespace="modules::comments::pres::templates"
      template="comment"
      categorykey="****"
  />
The template named comment includes the heading
  <a name="comments" /><h2>Kommentare</h2>
and the inclusion
  <core:importdesign
      namespace="modules::comments::pres::templates"
      template="[coview = listing]"
      incparam="coview"
  />
This XML tag takes responsibility of wheather the list or the form is displayed by the URL parameter coview (coview like CommentView). The output of the list is done within the template file listing, that must be created in the namespace given before. The content of the file looks like:

<@controller namespace="modules::comments::pres::documentcontroller" file="comment_listing_v1_controller" class="comment_listing_v1_controller" @>
<core:addtaglib namespace="tools::html::taglib" prefix="html" class="getstring" />
<html:getstring namespace="modules::comments" config="language" entry="listing.text.1" /> <a href="<html:placeholder name="Link" />#comments" title="<html:getstring namespace="modules::comments" config="language" entry="listing.text.2.title" />"><strong><html:getstring namespace="modules::comments" config="language" entry="listing.text.2" /></strong></a> <html:getstring namespace="modules::comments" config="language" entry="listing.text.3" />
<br />
<br />
<html:placeholder name="Pager" />
<html:placeholder name="Content" />

<html:template name="ArticleComment">
  <table cellspacing="0" cellpadding="0" style="border: 0px solid black; width: 100%;">
    <tr>
      <td style="width: 60px; text-align: left; padding: 2px;">
        <font style="font-size: 36px; color: #777BB4; font-style: italic;">
          <template:placeholder name="Number" />
        </font>
      </td>
      <td style="padding: 2px; font-size: 12px; font-family: Arial, Helvetica, sans-serif;">
        <strong><template:placeholder name="Name" /></strong>
        <br />
        <em><template:placeholder name="Date" />, <template:placeholder name="Time" /></em>
      </td>
    </tr>
  </table>
  <div style="background-color: #eeeeee; width: 100%; padding: 2px;">
    <template:placeholder name="Comment" />
  </div>
  <br />
</html:template>

<html:template name="NoEntries">
  <template:addtaglib namespace="tools::html::taglib" prefix="template" class="getstring" />
  <br />
  <span style="color: #777BB4; font-style: italic; margin-left: 30px; font-weight: bold;"><template:getstring namespace="modules::comments" config="language" entry="noentries.text" /></span>
  <br />
  <br />
  <br />
</html:template>

<html:template name="Deactivated">
  <template:addtaglib namespace="tools::html::taglib" prefix="template" class="getstring" />
  <br />
  <span style="color: #777BB4; font-style: italic; margin-left: 30px; font-weight: bold;"><template:getstring namespace="modules::comments" config="language" entry="deactivated.text" /></span>
  <br />
  <br />
  <br />
</html:template>

This template contains the controller definition, an introduction, place holders for the output of the pager and the output of the comment list and templates that define the appearance of the entries and the list. If the list takes no entries a message is displayed. To get a closer understanding of the tags used here, please refer to the standard taglibs section. Much more interesting is the implementation of the document controller that generates the output.

To make things clearer I will include a little excursion into GUI design of the adventure php framework here. The following lines explain the functionality of a tag by example:
The page controller component creates a DOM node in the global GUI object tree for each XML tag contained in a template file. Each node knows his father node by a reference, that is stored in the $this->__ParentObject member. So it is possible to read or write attributes of a neighbor DOM object (parent or child objects). Further code parts will make use of this matter of fact. The current design that the comment function is included by an XML tag that is configured by it's tag attributes. This node includes another template that generates the listing or the form. The node mentioned last needs access to the configuration parameter included in the <core:importdesign /> tag written to the template file mentioned at first to get the right category. Each document controller stores a reference on the current DOM node in the member variable Document. The following code shows how to get the content of the category key:
   $DocParent = &$this->__Document->getByReference('ParentObject');
   
$this->__CategoryKey $DocParent->getAttribute('categorykey'); 
To make the core more secure the value is checked with the following code fragment:
   if($CategoryKey == null){
      
$this->__CategoryKey 'standard';
   
// end if
   
}
   else{
      
$this->__CategoryKey $CategoryKey;
   
// end else
   
As this function must be included in both document controllers (generation of the list and the form controller) this function is sourced out into a basic document controller. This controller is named commentBaseController and stored under /apps/modules/coments/pres/documentcontroller. This abstract controller is also used to include the domain object and the manager. All of the concrete implementations inherit from the commentBaseController and thus inherit all of the functionality included there.

The concrete document controller comment_listing_v1_controller (see definition in the template file above) now contains the functionality to display the entries and the pager or display a message that no entries are made. This task can be solved like printed in the next code box:

<?php
   import
('modules::comments::pres::documentcontroller','commentBaseController');
   
import('tools::datetime','dateTimeManager');
   
import('tools::string','bbCodeParser');
   
import('tools::link','frontcontrollerLinkHandler');


   
/**
   *  @package modules::comments::pres::documentcontroller
   *  @class comment_listing_v1_controller
   *
   *  Implementiert den DocumentController für ds Template 'listing.html'.<br />
   *
   *  @author Christian Achatz
   *  @version
   *  Version 0.1, 22.08.2007<br />
   *  Version 0.2, 20.04.2008 (AllowedServers angepasst)<br />
   *  Version 0.3, 12.06.2008 (Anzeige-Quickhack entfernt)<br />
   */
   
class comment_listing_v1_controller extends commentBaseController
   
{

      function 
comment_listing_v1_controller(){
      }


      
/**
      *  @public
      *
      *  Implementiert die abstrakte Methode "transformContent()".<br />
      *
      *  @author Christian Achatz
      *  @version
      *  Version 0.1, 22.08.2007<br />
      *  Version 0.2, 02.09.2007 (Funktion auf anderen Hosts deaktiviert, dass bei Demopackages keine Fehler auftreten)<br />
      *  Version 0.3, 09.03.2008 (Deaktivierung wegen Indexer verändert)<br />
      *  Version 0.4, 12.06.2008 (Anzeige-Quickhack entfernt)<br />
      */
      
function transformContent(){

         
// Kategorie-Schlüssel laden
         
$this->__loadCategoryKey();

         
// Mapper holen
         
$M = &$this->__getAndInitServiceObject('modules::comments::biz','commentManager',$this->__CategoryKey);

         
// Einträge laden
         
$Entries $M->loadEntries();

         
$Buffer = (string)'';
         
$Template__ArticleComment = &$this->__getTemplate('ArticleComment');
         
$bbCP = &$this->__getServiceObject('tools::string','bbCodeParser');

         for(
$i 0$i count($Entries); $i++){

            
// Werte setzen
            
$Template__ArticleComment->setPlaceHolder('Number',$i 1);
            
$Template__ArticleComment->setPlaceHolder('Name',$Entries[$i]->get('Name'));
            
$Template__ArticleComment->setPlaceHolder('Date',dateTimeManager::convertDate2Normal($Entries[$i]->get('Date')));
            
$Template__ArticleComment->setPlaceHolder('Time',$Entries[$i]->get('Time'));
            
$Template__ArticleComment->setPlaceHolder('Comment',$bbCP->parseText($Entries[$i]->get('Comment')));

            
// Template transformieren und zum Puffer hinzufügen
            
$Buffer .= $Template__ArticleComment->transformTemplate();

          
// end for
         
}

         
// Falls keine Artikel vorliegen, Hinweis anzeigen
         
if(count($Entries) < 1){
            
$Template__NoEntries = &$this->__getTemplate('NoEntries');
            
$Buffer $Template__NoEntries->transformTemplate();
          
// end if
         
}

         
// Ausgabe in Puffer setzen
         
$this->setPlaceHolder('Content',$Buffer);

         
// Pager einsetzen
         
$this->setPlaceHolder('Pager',$M->getPager('comments'));

         
// Hinzufüge-Link erstellen
         
$URLParameter $M->getURLParameter();

         
// Link per frontcontrollerLinkHandler generieren
         
$this->setPlaceHolder(
                               
'Link',
                               
frontcontrollerLinkHandler::generateLink(
                                                                        
$_SERVER['REQUEST_URI'],
                                                                        array(
                                                                              
$URLParameter['StartName'] => '',
                                                                              
$URLParameter['CountName'] => '',
                                                                              
'coview' => 'form'
                                                                        
)
                               )
         );

       
// end function
      
}

    
// end function
   
}
?>

Special to this implementation is the gathering of the category key at the beginning of the method transformContent() and dynamically generation of the links. The links must be generated like this, because the developer does not know in which application the module is integrated. To generate the links correctly the business component is asked to give back the URL parameters used by the pager. These parameters - originally defined in the DEFAULT_pager.ini file described in chapter 4.3 - are reset due to "cosmetical reasons". This causes the pager to return to the first page when the entry is done. This trick could also be done inside the business component of the comment module but is placed in the document controller in this case, though the document controller must generate links anyway. Here the frontcontrollerLinkHandler is used instead of the linkHandler to make sure that the links generated with this component do not contain front controller action definition fragments. The formating of the text of a comment is done by the bbCodeParser and the dateTimeManager.



5. Enhancements

The software described above is able to display comments generated by external tools such as PHPMyAdmin. Doing entries is not implemented yet. For this reason the software should be added the possibility to write comments step by step. The upgrade to the software is now done in a top-down approach. So let's start with the presentation layer.


5.1. Presentation layer

Chapter 4.4 describes the possibility to manage the content of a view by the URL parameter coview. The view created to display te form should be named form. As the domain object already declares the user should provide
  • Name
  • E-Mail
  • Kommentar
to the form. Using the form taglib the form definition may look like this:
  <html:form name="AddComment" method="post">
    <span style="margin-right: 10px;">Name:</span><form:text maxlength="100"
      name="Name" value="" class="eingabe_feld" style="width: 390px;" validate="true"
      button="Save" validator="Text" />
    <br />
    <span style="margin-right: 10px;">E-Mail:</span><form:text maxlength="100"
      name="EMail" value="" class="eingabe_feld" style="width: 390px;" validate="true"
      button="Save" validator="EMail" />
    <br />
    <br />
    Kommentar:
    <br />
    <form:area name="Comment" class="eingabe_feld" style="width: 438px; height: 120px; overflow: auto;"
      validate="true" button="Save" validator="Text" />
    <br />
    <br />
    <form:button name="Save" value="Save" class="eingabe_feld" />
  </html:form>
Note: the attributes validate="true" and button="Speichern" activate the form validation and validator="Text" or validator="EMail" declares the validator the field should be validated with. Adding a little more text the template file form contains the following XML and HTML code:

<@controller namespace="modules::comments::pres::documentcontroller" file="comment_form_v1_controller" class="comment_form_v1_controller" @>
<core:addtaglib namespace="tools::form::taglib" prefix="html" class="form" />
<core:addtaglib namespace="tools::html::taglib" prefix="html" class="getstring" />
<html:getstring namespace="modules::comments" config="language" entry="formhint.text.1" /> <a href="<html:placeholder name="Zurueck" />#comments" title="<html:getstring namespace="modules::comments" config="language" entry="formhint.text.2.title" />"><strong><html:getstring namespace="modules::comments" config="language" entry="formhint.text.2" /></strong></a><html:getstring namespace="modules::comments" config="language" entry="formhint.text.3" />
<br />
<br />
<html:placeholder name="Form"/>

<html:form name="AddComment" method="post">
  <span style="margin-right: 10px;"><form:getstring namespace="modules::comments" config="language" entry="form.name" />*</span><form:text maxlength="100" name="Name" value="" class="eingabe_feld" style="width: 390px;" validate="true" button="Save" validator="Text" />
  <br />
  <span style="margin-right: 10px;"><form:getstring namespace="modules::comments" config="language" entry="form.email" />*</span><form:text maxlength="100" name="EMail" value="" class="eingabe_feld" style="width: 390px;" validate="true" button="Save" validator="EMail" />
  <br />
  <br />
  <form:getstring namespace="modules::comments" config="language" entry="form.comment" />
  <br />
  <form:area name="Comment" class="eingabe_feld" style="width: 441px; height: 120px; overflow: auto;" validate="true" button="Save" validator="Text" />
  <br />
  <br />
  <span style="margin-right: 10px;"><form:getstring namespace="modules::comments" config="language" entry="form.confirm" />*</span>
  <br />
  <br />
  <img src="<form:placeholder name="CaptchaImage" />" border="0" align="absmiddle" />
  <form:text name="CaptchaString" class="eingabe_feld" style="margin-left: 10px; width: 60px;" validate="true" button="Save" validator="Text" maxlength="5" />
  <br />
  <br />
  <form:button name="Save" class="eingabe_feld" />
</html:form>

The document controller belonging to this template file (comment_form_v1_controller) is entrusted with the generation of the form and the filling of the place holders defined there. In case the form was filled correctly the entry must be stored using the business component. For this reason the business component must be added the saveEntry() method that takes a domain object as an argument. The code box shows the complete PHP code:

<?php
   import
('modules::comments::pres::documentcontroller','commentBaseController');
   
import('tools::variablen','variablenHandler');
   
import('modules::comments::biz','commentManager');
   
import('tools::link','frontcontrollerLinkHandler');
   
import('tools::string','stringAssistant');


   
/**
   *  @package modules::comments::pres::documentcontroller
   *  @class comment_form_v1_controller
   *
   *  Implementiert den DocumentController für das Template 'form.html'.<br />
   *
   *  @author Christian Achatz
   *  @version
   *  Version 0.1, 22.08.2007
   */
   
class comment_form_v1_controller extends commentBaseController
   
{

      
/**
      *  @private
      *  Hält lokal verwendete Variablen
      */
      
var $_LOCALS = array();


      
/**
      *  @public
      *
      *  Konstruktor der Klasse.<br />
      *
      *  @author Christian Achatz
      *  @version
      *  Version 0.1, 22.08.2007<br />
      *  Version 0.2, 28.12.2007 (CaptchaString eingefügt)<br />
      */
      
function comment_form_v1_controller(){
         
$this->_LOCALS variablenHandler::registerLocal(array('Name','EMail','Comment','CaptchaString'));
       
// end function
      
}


      
/**
      *  @public
      *
      *  Implementiert die abstrakte Methode "transformContent()".
      *
      *  @author Christian Achatz
      *  @version
      *  Version 0.1, 22.08.2007<br />
      *  Version 0.2, 08.11.2007 (Mehrsprachigkeit eingebaut)<br />
      *  Version 0.3, 28.12.2007 (Captcha eingefügt)<br />
      */
      
function transformContent(){

         
// Referenz auf das Formular holen
         
$Form__AddComment = &$this->__getForm('AddComment');

         
// Prüfen, ob Formular abgesendet und erforlgreich validiert wurde
         
if($Form__AddComment->get('isSent') == true){

            
// Kategorie-Schlüssel laden
            
$this->__loadCategoryKey();

            
// Mapper holen
            
$M = &$this->__getAndInitServiceObject('modules::comments::biz','commentManager',$this->__CategoryKey);

            
// Validieren des Captchas
            
$CaptchaString $M->get('CaptchaString');

            if(
$CaptchaString != $this->_LOCALS['CaptchaString']){
               
$Captcha = &$Form__AddComment->getFormElementByName('CaptchaString');
               
$Captcha->set('isValid',false);
               
$Form__AddComment->set('isValid',false);
             
// end if
            
}

            
// Prüfen, ob Formular korrekt ausgefüllt wurde
            
if($Form__AddComment->get('isValid') == true){

               
// Eintrag erstellen
               
$ArticleComment = new ArticleComment();
               
$ArticleComment->set('Name',$this->_LOCALS['Name']);
               
$ArticleComment->set('EMail',$this->_LOCALS['EMail']);
               
$ArticleComment->set('Comment',$this->_LOCALS['Comment']);

               
// Eintrag speichern
               
$M->saveEntry($ArticleComment);

             
// end if
            
}
            else{
               
$this->__buildForm();
             
// end else
            
}

          
// end if
         
}
         else{
            
$this->__buildForm();
          
// end else
         
}

       
// end function
      
}


      
/**
      *  @private
      *
      *  Erzeugt das Formular.
      *
      *  @author Christian Achatz
      *  @version
      *  Version 0.1, 28.12.2008
      */
      
function __buildForm(){

         
// Referenz auf das Formular holen
         
$Form__AddComment = &$this->__getForm('AddComment');

         
// action setzen
         
$Form__AddComment->setAttribute('action',$_SERVER['REQUEST_URI'].'#comments');

         
// Button beschriften
         
$Config = &$this->__getConfiguration('modules::comments','language');
         
$Button = &$Form__AddComment->getFormElementByName('Save');
         
$Button->setAttribute('value',$Config->getValue($this->__Language,'form.button'));

         
// CaptchaImage füllen
         
$Form__AddComment->setPlaceHolder('CaptchaImage','/~/modules_comments-action/showCaptcha');

         
// Formular darstellen
         
$this->setPlaceHolder('Form',$Form__AddComment->transformForm());

         
// Zurücklink darstellen
         
$Link frontcontrollerLinkHandler::generateLink($_SERVER['REQUEST_URI'],array('coview' => 'listing'));
         
$this->setPlaceHolder('Zurueck',$Link);

       
// end function
      
}

    
// end class
   
}
?>


5.2. Business layer

In this chapter the task is to implement the saveEntry() function that was described in the section before. Merely the domain object must be saved and the corresponding view must be displayed. In order to save the object the data layer component must be involved to save the domain object. The following code box shows how to save the comment by the the manager's saveArticleComment() method. To display the listing view the user is redirected to the desired URL.
   function saveEntry($ArticleComment){

      
// get the mapper
      
$M = &$this->__getServiceObject('modules::comments::data','commentMapper');

      
// save article
      
$ArticleComment->set('CategoryKey',$this->__CategoryKey);
      
$M->saveArticleComment($ArticleComment);

      
// redirect to the listing view
      
$Link frontcontrollerLinkHandler::generateLink($_SERVER['REQUEST_URI'],array('coview' => 'listing'));
      
header('Location: '.$Link.'#comments');

    
// end function
   
Line 6 ($ArticleComment->set('CategoryKey'..) shows how the business layer manipulates the domain object, that it is saved in the right category. Line 10 generates the redirect URL using the frontcontrollerLinkHandler.


5.3. Data layer

Within the data layer only the saveArticleComment() method must be implemented: