Comment function
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:
class ArticleComment extends coreObject
{
var $__ID = null;
var $__Name;
var $__EMail;
var $__Comment;
var $__Date;
var $__Time;
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
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:
import('modules::comments::pres::documentcontroller','commentBaseController');
import('tools::datetime','dateTimeManager');
import('tools::string','bbCodeParser');
import('tools::link','frontcontrollerLinkHandler');
class comment_listing_v1_controller extends commentBaseController
{
function comment_listing_v1_controller(){
}
function transformContent(){
// get category key
$this->__loadCategoryKey();
// get data layer component
$M = &$this->__getAndInitServiceObject('modules::comments::biz','commentManager',$this->__CategoryKey);
// load entries
$Entries = $M->loadEntries();
$Buffer = (string)'';
$Template__ArticleComment = &$this->__getTemplate('ArticleComment');
$bbCP = &$this->__getServiceObject('tools::string','bbCodeParser');
for($i = 0; $i < count($Entries); $i++){
// fill template
$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')));
// add template to list
$Buffer .= $Template__ArticleComment->transformTemplate();
// end for
}
// display hint in cas of no entries
if(count($Entries) < 1){
$Template__NoEntries = &$this->__getTemplate('NoEntries');
$Buffer = $Template__NoEntries->transformTemplate();
// end if
}
// display list
$this->setPlaceHolder('Content',$Buffer);
// display pager
$this->setPlaceHolder('Pager',$M->getPager('comments'));
// get pager url params
$URLParameter = $M->getURLParameter();
// generate create entry link
$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
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:
import('modules::comments::pres::documentcontroller','commentBaseController');
import('tools::variablen','variablenHandler');
import('modules::comments::biz','commentManager');
import('tools::link','frontcontrollerLinkHandler');
import('tools::string','stringAssistant');
class comment_form_v1_controller extends commentBaseController
{
var $_LOCALS = array();
function comment_form_v1_controller(){
$this->_LOCALS = variablenHandler::registerLocal(array('Name','EMail','Comment','CaptchaString'));
// end function
}
function transformContent(){
// get form
$Form__AddComment = &$this->__getForm('AddComment');
// check, if form was sent
if($Form__AddComment->get('isSent') == true){
// get category key
$this->__loadCategoryKey();
// get business layer component
$M = &$this->__getAndInitServiceObject('modules::comments::biz','commentManager',$this->__CategoryKey);
// get captcha string and handle captcha control
$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
}
// check if form is valid
if($Form__AddComment->get('isValid') == true){
// create entry domain object
$ArticleComment = new ArticleComment();
$ArticleComment->set('Name',$this->_LOCALS['Name']);
$ArticleComment->set('EMail',$this->_LOCALS['EMail']);
$ArticleComment->set('Comment',$this->_LOCALS['Comment']);
// save entry
$M->saveEntry($ArticleComment);
// end if
}
else{
$this->__buildForm();
// end else
}
// end if
}
else{
$this->__buildForm();
// end else
}
// end function
}
function __buildForm(){
// get form
$Form__AddComment = &$this->__getForm('AddComment');
// set action
$Form__AddComment->setAttribute('action',$_SERVER['REQUEST_URI'].'#comments');
// label button
$Config = &$this->__getConfiguration('modules::comments','language');
$Button = &$Form__AddComment->getFormElementByName('Save');
$Button->setAttribute('value',$Config->getValue($this->__Language,'form.button'));
// fill CaptchaImage
$Reg = &Singleton::getInstance('Registry');
$URLRewriting = $Reg->retrieve('apf::core','URLRewriting');
if($URLRewriting === true){
$Form__AddComment->setPlaceHolder('CaptchaImage','/~/modules_comments-action/showCaptcha');
// end if
}
else{
$Form__AddComment->setPlaceHolder('CaptchaImage','./?modules_comments-action:showCaptcha');
// end else
}
// display form
$this->setPlaceHolder('Form',$Form__AddComment->transformForm());
// display back link
$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:
function saveArticleComment($ArticleComment){
// get the MySQLHandler
$SQL = &$this->__getServiceObject('core::database','MySQLHandler');
// check if the comment already exists
if($ArticleComment->get('ID') == null){
$insert = 'INSERT INTO article_comments
(Name, EMail, Comment, Date, Time, CategoryKey)
VALUES
(\''.$ArticleComment->get('Name').'\',
\''.$ArticleComment->get('EMail').'\',
\''.$ArticleComment->get('Comment').'\',
CURDATE(),
CURTIME(),
\''.$ArticleComment->get('CategoryKey').'\');';
$SQL->executeTextStatement($insert);
// end if
}
// end function
}
The meaning of the lines of code is easy to understand. At first a singleton instance of the
MySQLHandler is generated by the __getServiceObject() function. After that the
method checks wheather the object already exists and saves the object by a SQL statement if not.
During the insert the object is deconstructed into a flat and relational structure again.
6. Perspective / additions
In this chapter the author would like to point out to the fact that the methods
__getServiceObject() and __getAndInitServiceObject() must be used
when the service layer generated with it wants to load context or language dependent configuration or
use context dependent components. Therefore, in database based applications both methods should
always be used.
Comments
Do you want to add a comment to the article above, or do you want to post additional hints? So please click here. Comments already posted can be found below.
There are no comments belonging to this article.