Comment function
This page contains deprecated components, that are not contained in
release 1.11. Thus, this page now is
under development until this message is removed. If you have, any question, please create a new
thread in our
support forum.
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.
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:
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.
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.
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.
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.
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.
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.
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.
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:
Code
/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.
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-Code
class ArticleComment extends APFObject {
protected $__ID = null;
protected $__Name;
protected $__EMail;
protected $__Comment;
protected $__Date;
protected $__Time;
protected $__CategoryKey;
}
Given the fact that the class inherits from
APFObject no
get() or
set() methods must be created, because the
APFObject 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
PHP-Code
$AC = new ArticleComment();
$AC->set('Name','Max Mustermann');
echo $AC->get('Name');
The output in this case would be
APF-Template
Max Mustermann
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:
PHP-Code
import('modules::comments::biz','ArticleComment');
import('core::database','ConnectionManager');
class commentMapper extends APFObject {
function loadArticleCommentByID($articleCommentID){
// get a database connnection
$SQL = &$this->__getConnection();
// 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));
}
function __mapArticleComment2DomainObject($ResultSet){
$ArticleComment = new ArticleComment();
if(isset($ResultSet['ArticleCommentID'])){
$ArticleComment->set('ID',$ResultSet['ArticleCommentID']);
}
if(isset($ResultSet['Name'])){
$ArticleComment->set('Name',$ResultSet['Name']);
}
if(isset($ResultSet['EMail'])){
$ArticleComment->set('EMail',$ResultSet['EMail']);
}
if(isset($ResultSet['Comment'])){
$ArticleComment->set('Comment',$ResultSet['Comment']);
}
if(isset($ResultSet['Date'])){
$ArticleComment->set('Date',$ResultSet['Date']);
}
if(isset($ResultSet['Time'])){
$ArticleComment->set('Time',$ResultSet['Time']);
}
return $ArticleComment;
}
private function &__getConnection(){
$cM = &$this->__getServiceObject('core::database','ConnectionManager');
$config = $this->__getConfiguration('modules::comments','comments');
$connectionKey = $config->getValue('Default','Database.ConnectionKey');
return $cM->getConnection($connectionKey);
}
}
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.
-
The funktion __getConnection() returns the desired database connection as configured
in the module's configuration file.
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.
The configuration file
{ENVIRONMENT}_comments.ini contains a reference on a database
connection. The latter one is defines within the database connection setup in the
core::database namespace. Details on the database connection configuration can be taken
from the chapter
ConnectionManager.
The source code printed in this example does not contain security mechanisms to avoid SQL
injections. Nevertheless, these should be included in real-life applications! For this reason,
the
MySQLxHandler does contain the
escapeValue() method. This function can be
used to escape control characters and do form a input good security along with the
input filters. The
security chapter contains some more notes on
the built-in mechanisms of the APF.
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:
PHP-Code
import('modules::pager::biz','PagerManagerFabric');
import('modules::comments::data','commentMapper');
class commentManager extends APFObject {
private $__CategoryKey;
function init($CategoryKey){
$this->__CategoryKey = $CategoryKey;
}
function loadEntries(){
}
}
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:
PHP-Code
// 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');
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:
APF-Konfiguration
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ArticleComments ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[ArticleComments]
; database connection key
Pager.DatabaseConnection = "..."
; 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:
Code
SELECT COUNT(*) AS EntriesCount
FROM article_comments
WHERE CategoryKey = '[CategoryKey]'
GROUP BY CategoryKey;
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:
Code
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:
PHP-Code
function loadEntries(){
// get pagerManager
$pMF = &$this->__getServiceObject('modules::pager::biz','PagerManagerFabric');
$pM = &$pMF->getPagerManager('ArticleComments');
// load comments
$M = &$this->__getServiceObject('modules::comments::data','commentMapper');
return $pM->loadEntriesByAppDataComponent($M,'loadArticleCommentByID',array('CategoryKey' => $this->__CategoryKey));
}
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:
PHP-Code
function loadEntries(){
// get pagerManager
$pMF = &$this->__getServiceObject('modules::pager::biz','PagerManagerFabric');
$pM = &$pMF->getPagerManager('ArticleComments');
// load IDs
$EntryIDs = $pM->loadEntries(array('CategoryKey' => $this->__CategoryKey));
// load comments
$M = &$this->__getServiceObject('modules::comments::data','commentMapper');
$Entries = array();
for($i = 0; $i < count($EntryIDs); $i++){
$Entries[] = $M->loadArticleCommentByID($EntryIDs[$i]);
}
return $Entries;
}
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.
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
APF-Template
<core:importdesign
namespace="modules::comments::pres::templates"
template="comment"
categorykey="****"
/>
The template named
comment includes the heading
APF-Template
<a name="comments" /><h2>Kommentare</h2>
and the inclusion
APF-Template
<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:
APF-Template
<@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" />
<div class="cm--list">
<div class="cm--list-head">
<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" />
</div>
<div class="cm--list-pager">
<html:placeholder name="Pager" />
</div>
<div class="cm--list-items">
<html:placeholder name="Content" />
</div>
</div>
<html:template name="ArticleComment">
<div class="cm--list-item">
<div class="cm--list-item-head">
<div class="cm--list-item-head-num"><template:placeholder name="Number" /></div>
<div class="cm--list-item-head-date">
<span><template:placeholder name="Name" /></span>
<em><template:placeholder name="Date" />, <template:placeholder name="Time" /></em>
</div>
</div>
<div class="cm--list-item-body">
<template:placeholder name="Comment" />
</div>
</div>
</html:template>
<html:template name="NoEntries">
<template:addtaglib namespace="tools::html::taglib" prefix="template" class="getstring" />
<div class="cm--list-noentries">
<template:getstring namespace="modules::comments" config="language" entry="noentries.text" />
</div>
</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:
PHP-Code
$DocParent = &$this->__Document->getParentObject();
$this->__CategoryKey = $DocParent->getAttribute('categorykey');
To make the core more secure the value is checked with the following code fragment:
PHP-Code
if($CategoryKey == null){
$this->__CategoryKey = 'standard';
}
else{
$this->__CategoryKey = $CategoryKey;
}
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-Code
class comment_listing_v1_controller extends commentBaseController {
function transformContent(){
$this->__loadCategoryKey();
$M = &$this->__getAndInitServiceObject('modules::comments::biz','commentManager',$this->__CategoryKey);
// load the entries using the business component
$entries = $M->loadEntries();
$buffer = (string)'';
$template = &$this->__getTemplate('ArticleComment');
// init bb code parser (remove some provider, that we don't need configuration files)
$bP = &$this->__getServiceObject('tools::string','AdvancedBBCodeParser');
$bP->removeProvider('standard.font.color');
$bP->removeProvider('standard.font.size');
for($i = 0; $i < count($entries); $i++){
$template->setPlaceHolder('Number',$i + 1);
$template->setPlaceHolder('Name',$entries[$i]->get('Name'));
$template->setPlaceHolder('Date',dateTimeManager::convertDate2Normal($entries[$i]->get('Date')));
$template->setPlaceHolder('Time',$entries[$i]->get('Time'));
$template->setPlaceHolder('Comment',$bP->parseCode($entries[$i]->get('Comment')));
$buffer .= $template->transformTemplate();
}
// display hint, if no entries are to display
if(count($entries) < 1){
$Template__NoEntries = &$this->__getTemplate('NoEntries');
$buffer = $Template__NoEntries->transformTemplate();
}
// display the list
$this->setPlaceHolder('Content',$buffer);
// display the pager
$this->setPlaceHolder('Pager',$M->getPager('comments'));
// get the pager url params from the business component
// to be able to delete them from the url.
$urlParams = $M->getURLParameter();
// generate the add comment link
$this->setPlaceHolder(
'Link',
FrontcontrollerLinkHandler::generateLink(
$_SERVER['REQUEST_URI'],
array(
$urlParams['StartName'] => '',
$urlParams['CountName'] => '',
'coview' => 'form'
)
)
);
}
}
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.
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.
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:
APF-Template
<html:form name="AddComment" method="post">
<span>
<form:getstring namespace="modules::comments" config="language" entry="form.name" />*
</span>
<form:text
maxlength="100"
name="Name"
class="cm--create-element-name"
validate="true"
button="Save"
validator="Text"
/>
<br />
<span>
<form:getstring namespace="modules::comments" config="language" entry="form.email" />*
</span>
<form:text
maxlength="100"
name="EMail"
class="cm--create-element-email"
validate="true"
button="Save"
validator="EMail"
/>
<br />
<br />
<form:getstring namespace="modules::comments" config="language" entry="form.comment" />
<br />
<form:area
name="Comment"
class="cm--create-element-comment"
validate="true"
button="Save"
validator="Text"
/>
<br />
<br />
<span>
<form:getstring namespace="modules::comments" config="language" entry="form.confirm" />*
</span>
<br />
<br />
<form:addtaglib namespace="modules::captcha::pres::taglib" prefix="form" class="captcha" />
<form:captcha
text_class="cm--create-element-captcha"
validate="true"
button="Save"
clearonerror="true"
/>
<br />
<br />
<form:button name="Save" class="cm--create-element-button" />
</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:
APF-Template
<@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" />
<div class="cm--create">
<div class="cm--create-head">
<html:getstring namespace="modules::comments" config="language" entry="formhint.text.1" /> <a href="<html:placeholder name="back" />#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" />
</div>
<div class="cm--create-form">
<html:form name="AddComment" method="post">
<span>
<form:getstring namespace="modules::comments" config="language" entry="form.name" />*
</span>
<form:text
maxlength="100"
name="Name"
class="cm--create-element-name"
validate="true"
button="Save"
validator="Text"
/>
<br />
<span>
<form:getstring namespace="modules::comments" config="language" entry="form.email" />*
</span>
<form:text
maxlength="100"
name="EMail"
class="cm--create-element-email"
validate="true"
button="Save"
validator="EMail"
/>
<br />
<br />
<form:getstring namespace="modules::comments" config="language" entry="form.comment" />
<br />
<form:area
name="Comment"
class="cm--create-element-comment"
validate="true"
button="Save"
validator="Text"
/>
<br />
<br />
<span>
<form:getstring namespace="modules::comments" config="language" entry="form.confirm" />*
</span>
<br />
<br />
<form:addtaglib namespace="modules::captcha::pres::taglib" prefix="form" class="captcha" />
<form:captcha
text_class="cm--create-element-captcha"
validate="true"
button="Save"
clearonerror="true"
/>
<br />
<br />
<form:button name="Save" class="cm--create-element-button" />
</html:form>
</div>
</div>
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-Code
class comment_form_v1_controller extends commentBaseController {
function transformContent(){
$form = &$this->__getForm('AddComment');
if($form->get('isSent') == true){
$this->__loadCategoryKey();
$M = &$this->__getAndInitServiceObject('modules::comments::biz','commentManager',$this->__CategoryKey);
if($form->get('isValid') == true){
$articleComment = new ArticleComment();
$name = &$form->getFormElementByName('Name');
$articleComment->set('Name',$name->getAttribute('value'));
$email = &$form->getFormElementByName('EMail');
$articleComment->set('EMail',$email->getAttribute('value'));
$comment = &$form->getFormElementByName('Comment');
$articleComment->set('Comment',$comment->getContent());
$M->saveEntry($articleComment);
}
else{
$this->__buildForm();
}
}
else{
$this->__buildForm();
}
}
private function __buildForm(){
$form = &$this->__getForm('AddComment');
$form->setAttribute('action',$_SERVER['REQUEST_URI'].'#comments');
$config = &$this->__getConfiguration('modules::comments','language');
$button = &$form->getFormElementByName('Save');
$button->setAttribute('value',$config->getValue($this->__Language,'form.button'));
$form->transformOnPlace();
$link = FrontcontrollerLinkHandler::generateLink($_SERVER['REQUEST_URI'],array('coview' => 'listing'));
$this->setPlaceHolder('back',$link);
}
}
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.
PHP-Code
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');
}
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.
Within the data layer only the
saveArticleComment() method must be implemented:
PHP-Code
function saveArticleComment($ArticleComment){
$SQL = &$this->__getConnection();
// 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);
}
}
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.
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.
1
giobien5
24.10.2009, 09:25:29
please upload source for everyone demo visualy
2
Christian
01.11.2009, 21:31:43
Hello giobien5,
the source code of the comment module is included in the APF release. You can find the code in the apps/modules/kontakt4/ folder.