Behind the site

1. Introduction

This page is intended to be a guide to application development. It describes the design of the present documentation page and shows, how real life problems can easily be solved using the adventure php framework. Further, this page presents several source code examples to you, that can be used for your applications, as well.

At first, the article describes the software design of the page. After that, the dynamic perspective and view concept is discussed. Then, several examples are presented containing source code as well as some explanations on this issues.


2. The software design

As you have already mentioned, this page features two main layouts: the start page and the documentation pages. Some of the elements presented in those - we call them - perspectives are similar, some are not. The challenge is now, to structure the software in a way, that you don't have duplicate code. Let us at fist have a look at the two perspectives and the areas, that can be defined:


2.1. Start perspective

Behind the site - start perspective (APF)

The areas have the following meaning:
  • Head: The head of the page contains the menu and the generic navigation (contact, impress). Within the menu, the search field is presented as well.
  • Teaser: The teaser area contains the logo, the claim and some important links.
  • Tutorials: The tutorials box contains a list of available tutorials.
  • Downloads: The downloads box contains the latest downloads and links to the documentation and the subversion core repository.
  • Latest: The latest entries box contains a list of the two latest forum entries and user comments on the documentation. These information are read from the respective databases.
  • Content 1: The first content area contains three tabs, that present APF references, a weekly implementation hint and links to related projects.
  • Content 2: The second content area features two tabs: a list of articles and the news tab, that contains a short sentence on the news of the adventure php framework's code or the documentation page itself.

2.2. Content perspective

Behind the site - content perspective (APF)

The areas have the following meaning:
  • Head: The head of the page contains the menu and the generic navigation (contact, impress). Within the menu, the search field is presented as well.
  • Breadcrump: The breadcump path shows where you are on the page and presents the home link to you.
  • Content: The content area contains the documentation content.
  • Quicknavi: The quicknavi area contains a kind of subnavigation that can be used to navigate with a single dcumentation page. Without it, the standard taglibs page would be horrible.

2.3. Perspectives, areas and views

The software design of this documentation page differentiates between three types of structural elements: the perspectives, the areas and the views. The perspective is the main "layout switch". Within each of the two perspectives, the <*:importdesign/> tags contained in the templates define the areas, that contain the views, that should be presented to the visitor. The are is also a simple <*:importdesign/> tag definition. The view itself is a template, that is intended to generate dynamic content. This concept makes the page flexible and easy to maintain and to extend in further times. If you one time decide to add a print perspective, you only have to define a new perspective template containing the desired areas!

Within this documentation page, the name of the perspective is represened in the global application model (APFModel) and the area-to-view-mapping is statically. Hence, the main template contains a dynamic importdesign tag and the perspective templates only static import definitions.

Due to the fact, that the head view is used within the start and the content perspective, the menu (view-)template is defined globally and imported in each of the two perspective templates. Also the generic navigation on the right top is a global (view-)template.


2.4. Multilanguage views

To be able to reuse one template for several languages, the templates mentioned above are created language independent. This means, that every template contains place holders, that are filled with the language dependet values at transformation time. This can be achieved using the <*:getstring/> tags or simple place holders that are filled within a document controller. Please note, that this function can only be implemented in the way described before, because each APF DOM node knows about it's language. Setting the root node to a specific language, all the child nodes will be applied this language.

The fact of language inheritance within the APF DOM tree is also used here to control the language of the view templates. For this reason, a global front controller action is executed on each request that checks the language set in the bootstrap file and analyzes the url keywords and thus decides which language the page currently has got. The action then manipulates the language param of the front controller, and that way the language of the page's DOM tree, too.


2.5. Business layer

As already mentioned above for some times, the documentation page features a dedicated business layer, that cares about the perspective to display and the current language of the page. It also handles the page sensitive infotmation like html meta data (keywords, description, ...) that are used by the perspective controller to fill the meta data.

Another task of the business layer is the statistic recording. To achieve this, another action was implemented, that reads the relevant information from the application model and stores the request in a database table. Te ensure, that the statistic is written after the page is delivered, the action is given the type posttransform.


3. Perspective implementation

As we have already discussed the theory of perspectives, this section should give you advice, how to implement this concept.


3.1. Main template

The main template of the page contains a single import design definition, that uses the application model's information to load the suitable persective template:
APF-Template
<core:addtaglib namespace="tools::html::taglib" prefix="fcon" class="importdesign" /> <fcon:importdesign templatenamespace="sites::apfdocupage::pres::templates::perspectives" modelnamespace="sites::apf::biz" modelfile="APFModel" modelclass="APFModel" modelparam="perspective.name" />
As you can get from the code box, the pres::templates namespace does not only contain the templates of the page, but a subfolder for the perspectives and another subfolder for the view templates of each perspective. This is done to have a clean folder structure with clear memberships. The following box shows the structure of the sites::apfdocupage namespace:
APF-Template
biz/ actions/ setmodel/ --> <em>Files for the application model action</em> stat/ --> <em>Files for the statistic action</em> data/ pres/ content/ --> <em>Content files for the content view</em> documentcontroller/ global/ --> <em>Controllers for menu and generic navigation</em> perspectives/ --> <em>Perspective controllers</em> content/ --> <em>Controllers of the view templates for the content perspective</em> start/ --> <em>Controllers of the view templates for the start perspective</em> quicknavi/ --> <em>Content files for the quicknavi view</em> taglib/ --> <em>Taglibs, that extend the APF's functionality</em> templates/ global/ --> <em>Templates for menu and generic navigation</em> perspectives/ <em>Perspective templates</em> content/ --> <em>View templates for the content perspective</em> start/ --> <em>View templates for the start perspective</em>

3.2. Perspective templates

The perspective templates contain further importdesign statements (=area definitions) to include the desired view templates. The include directives are choosen to be static, because the perspectives have no need to include dynamic views.

The content perspective template looks as follows (shortened):
APF-Template
<@controller namespace="sites::apfdocupage::pres::documentcontroller::perspectives" class="content_perspective_controller" file="content_perspective_controller" @> <html> <head> <title><html:placeholder name="Title" /> :: Adventure PHP Framework (APF)</title> </head> <body> <div id="head"> <div id="top"> <div id="small_menu"> <core:importdesign namespace="sites::apfdocupage::pres::templates::global" template="smallmenu" /> </div> <core:importdesign namespace="sites::apfdocupage::pres::templates::global" template="mainnavi" /> </div> </div> <div id="page"> <div id="teaser_small"> <core:importdesign namespace="sites::apfdocupage::pres::templates::perspectives::content" template="breadcrump" /> </div> <div id="doku_menu"> <div id="doku_menu_content"> <pre>Quicknavi</pre> <core:addtaglib namespace="sites::apfdocupage::pres::taglib" prefix="html" class="quicknavi" /> <html:quicknavi /> </div> </div> <div id="doku_page"> <div id="doku_page_content"> <core:importdesign namespace="sites::apfdocupage::pres::templates::perspectives::content" template="bookmark" /> <core:addtaglib namespace="sites::apfdocupage::pres::taglib" prefix="html" class="content" /> <html:content /> </div> </div> </div> </body> </html>
As you can see, the perspective template defines a document controller, that fills the language and/or page dependent values (e.g. title of the page). Moreover, the content and quicknavi content is included by special taglibs. Details on these taglibs are discussed in chapter 3.3.

The corresponding perspective document controller contains the following code:
PHP-Code
import('sites::apf::biz','APFModel'); class content_perspective_controller extends baseController { function transformContent(){ // get model $Model = &Singleton::getInstance('APFModel'); // set current title $this->setPlaceHolder('Title',$Model->getAttribute('page.title')); ... } }


3.3. Additional taglibs

As already quoted above, the content and quicknavi content is included by special taglibs. The reason is, that a normal importdesign tag does not meet the requirements at this point. Let me first give you an explanation on the reasons:
The content and quicknavi content files have a special naming convention. This means, that content files start with c_ then containing the id of the page (e.g. 079) and a descriptive name. Quicknavi follow the same naming convention, but start with a n_. This is because the pages on this documentation page are addressed by their page id. The tow taglibs now include application logic, that read the desired content or quicknavi file name from the model, read the content and parse the taglibs and document controller directives included there. To not have the need to announce tags within each content file, the tags define the known tags within their constructor.

Here's an example of the content taglib:
PHP-Code
import('sites::apf::biz','APFModel'); import('sites::apfdocupage::pres::taglib','php_taglib_highlight'); import('sites::apfdocupage::pres::taglib','html_taglib_highlight'); import('sites::apfdocupage::pres::taglib','doku_taglib_link'); import('sites::apfdocupage::pres::taglib','doku_taglib_title'); class html_taglib_content extends Document { function html_taglib_content(){ parent::Document(); $this->__TagLibs[] = new TagLib('sites::apfdocupage::pres::taglib','php','highlight'); $this->__TagLibs[] = new TagLib('sites::apfdocupage::pres::taglib','html','highlight'); $this->__TagLibs[] = new TagLib('sites::apfdocupage::pres::taglib','doku','link'); $this->__TagLibs[] = new TagLib('sites::apfdocupage::pres::taglib','doku','title'); } function onParseTime(){ // get model $Model = &Singleton::getInstance('APFModel'); // include the content of the model's content file in the current object $this->__Content = file_get_contents($Model->getAttribute('content.filepath').'/content/'.$Model->getAttribute('page.contentfilename')); // extract tag libs included in the content $this->__extractTagLibTags(); // extract document controller statements $this->__extractDocumentController(); } }
This approach gives you the flexibility to include further templates or functionality in the content and quicknavi files without changing the inclusion.


4. Management of page dependent values

The flexibility of the software design is built around the business layer and the application model. The latter one therefor stores all relevant data, that is needed to display the content and quicknavi, but also to fill the page dependent meta tags in the perspective. To achieve this goal, several mechanisms were implemented.


4.1. The application model

The application model is designed to be the central management instance of the present page. Thus, the model class defines the relevant parameters as class attributes:
PHP-Code
class APFModel extends coreObject { function APFModel(){ $this->__Attributes['content.filepath'] = '../apps/sites/apfdocupage/pres'; $this->__Attributes['perspective.name'] = 'start'; $this->__Attributes['page.language'] = null; $this->__Attributes['page.id'] = null; $this->__Attributes['page.title'] = null; $this->__Attributes['page.urlname'] = null; $this->__Attributes['page.description'] = null; $this->__Attributes['page.tags'] = null; $this->__Attributes['page.contentfilename'] = null; $this->__Attributes['page.quicknavifilename'] = null; $this->__Attributes['page.indicator'] = array( 'de' => 'Seite', 'en' => 'Page' ); } }
The attributes are then filled by the components described in the chapters 4.2. and 4.3.


4.2. The setmodel action

The setmodel action is one of the central application components. It is called on every request and extracts the url information and sets the
  • page.language
  • perspective.name
  • page.id
  • page.contentfilename
  • page.quicknavifilename
parameters of the APFModel. When the action is executed, the page controller is invoked by the front controller and the document tree is created. Here's the source code of the action:
PHP-Code
import('sites::apf::biz','APFModel'); import('tools::request','RequestHandler'); class SetModelAction extends AbstractFrontcontrollerAction { function run(){ // get model $Model = &Singleton::getInstance('APFModel'); // register request parameters $PageIndicatorNames = $Model->getAttribute('page.indicator'); $PageIndicatorNameDE = $PageIndicatorNames['de']; $PageIndicatorNameEN = $PageIndicatorNames['en']; $CurrentPageIndicators = RequestHandler::getValues( array($PageIndicatorNameDE => null,$PageIndicatorNameEN => null)); // check request parameters and set current language if($CurrentPageIndicators[$PageIndicatorNameDE] != null){ $Language = 'de'; $Model->setAttribute('page.language',$Language); $this->__ParentObject->set('Language',$Language); $this->__Language = $Language; $Model->setAttribute('perspective.name','content'); } elseif($CurrentPageIndicators[$PageIndicatorNameEN] != null){ $Language = 'en'; $Model->setAttribute('page.language',$Language); $this->__ParentObject->set('Language',$Language); $this->__Language = $Language; $Model->setAttribute('perspective.name','content'); } else{ // use default language of the front controller $Language = $this->__ParentObject->get('Language'); $Model->setAttribute('page.language',$Language); $Model->setAttribute('perspective.name','start'); } // fill current page id $PageName = $CurrentPageIndicators[$PageIndicatorNames[$Model->getAttribute('page.language')]]; $PageID = substr($PageName,0,3); $Model->setAttribute('page.id',$PageID); // fill the current content and quicknavi file name $ContentFilePath = $Model->getAttribute('content.filepath'); $Model->setAttribute('page.contentfilename',$this->__getFileName($ContentFilePath.'/content','c',$Language,$PageID)); $Model->setAttribute('page.quicknavifilename',$this->__getFileName($ContentFilePath.'/quicknavi','n',$Language,$PageID)); } function __getFileName($ContentFilePath,$Prefix,$Language,$PageID){ $ContentFiles = glob($ContentFilePath.'/'.$Prefix.'_'.$Language.'_'.$PageID.'*'); if(!isset($ContentFiles[0])){ return $Prefix.'_'.$Language.'_404.html'; } else{ return basename($ContentFiles[0]); } } }

4.3. The doku:title taglib

The <doku:title /> tag is indended to store and publish the meta information about the current page. To do so, the taglib takes the information if the tag itself and publishes them to the model. Due to the fact, that this is done during parse time (see onParseTime() for details), the information provided by the tag is available on transformation time and can then be taken from the model and written to a desired place holder.

The following code box shows the implementation of the taglib:
PHP-Code
import('sites::apf::biz','APFModel'); class doku_taglib_title extends Document { var $__Title = null; function onParseTime(){ // get page title if(!isset($this->__Attributes['title']) || empty($this->__Attributes['title'])){ trigger_error('[doku_taglib_title::onParseTime()] The attribute "title" is missing. Please provide the page title!',E_USER_ERROR); exit(1); } $this->__Title = $this->__Attributes['title']; // get page tags if(!isset($this->__Attributes['tags']) || empty($this->__Attributes['tags'])){ trigger_error('[doku_taglib_title::onParseTime()] The attribute "tags" is missing. Please provide the page meta tags!',E_USER_ERROR); exit(1); } $Tags = $this->__Attributes['tags']; // get urlname if(!isset($this->__Attributes['urlname']) || empty($this->__Attributes['urlname'])){ trigger_error('[doku_taglib_title::onParseTime()] The attribute "urlname" is missing. Please provide url name of the page!',E_USER_ERROR); exit(1); } $URLName = $this->__Attributes['urlname']; // get page description if(empty($this->__Content)){ trigger_error('[doku_taglib_title::onParseTime()] No page description given in the tag\'s content area. Please provide the page description!',E_USER_ERROR); exit(1); } // inform model $Model = &Singleton::getInstance('APFModel'); $Model->setAttribute('page.title',$this->__Title); $Model->setAttribute('page.description',str_replace(' ',' ',str_replace("\r",'',str_replace("\n",'',trim($this->__Content))))); $Model->setAttribute('page.tags',$Tags); $Model->setAttribute('page.urlname',$URLName); } ... }

4.4. Meta tag display

Due to the fact, that the meta information of a specific page is completely described within the model on transformation time, it is quite easy to display the page's meta tags in the perspective template. For this reason, the controller of the perspective template gets an instance of the model and fills the place holders:
PHP-Code
import('sites::apf::biz','APFModel'); class start_perspective_controller extends baseController { function transformContent(){ // get model $Model = &Singleton::getInstance('APFModel'); ... // fill current language $this->setPlaceHolder('Language',$Model->getAttribute('page.language')); } }

5. Multilanguage implementation

The multilanguage implementation implementation utilizes the fact, that each APF DOM node knows about it's language. This can be used to fill template place holders with language dependent values or display templates, that contain content for the desired target language.


5.1. Language dependent place holders

Language dependency on the layer of place holder is quite easy. Therefore, all language dependent content is replaced by place holders and the content of the place holder is filled by a document controller. This can be achieved by placing
APF-Template
<html:placeholder name="MyPlaceHolder" />
in your template and write something like
PHP-Code
if($this->__Language == 'en'){ $this->setPlaceHolder('MyPlaceHolder','My desired content'); } elseif($this->__Language == 'de'){ $this->setPlaceHolder('MyPlaceHolder','Mein gewuenschter Inhalt'); } else{ $this->setPlaceHolder('MyPlaceHolder','!!! ERROR: no language specific content is present !!!'); }
to the transform() method of your document controller.

Another approach is to use the <*:getstring /> tags. This - im my eyes - very elegant method enables you to store the language dependent content within a language configuration file. To do so, a language config file must be create containing the desired language sections and the relevant translations - each referenced by a configuration key. After that, you can place a <*:getstring /> in the desired scope (template file, html:template, html:form). This procedural method is used within the start perspective's template:
APF-Template
... <core:addtaglib namespace="tools::html::taglib" prefix="html" class="getstring" /> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de"> <head> <title><html:getstring namespace="sites::apfdocupage::pres" config="language" entry="title" /></title> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> ...
Here, the title is filled with the language dependent content from the configuration file
Code
/config/sites/apfdocupage/pres/{CONTEXT}/{ENVIRONMENT}_language.ini

5.2. Language dependent templates

For each of the boxes of the start perspective described in chapter 2.1., one single template is stored in the sites::apfdocupage::pres::tenplates::perspectives::start namespace. The template file contains two templates, that follow a naming convention. The English content must be encapsulated by a template with the name Content_en, the German content must be included in the Content_de template. In order to display the content in language dependent manner, the document controller picks the template suitable for the current language and displays it. This can be done by
PHP-Code
class i_box_standard_controller extends baseController { function transformContent(){ $Template__Content = &$this->__getTemplate('Content_'.$this->__Language); $Template__Content->transformOnPlace(); } }

6. Tools used

The follwing list presents the tools used within the implementation of the documentation page. For each list entry a brief desciption tells you how and for which tasks the component is used:
  • Front controller:
    The front controller's action mechanism is used to set up the application model before the presentation layer is created. After the page is transformed, the statistic action is called to log each request for future analysis.

  • Page controller:
    The page controller's flexibility of template handling and DOM tree manipulation is used to implement the perspective-area-view concept.

  • connectionManager:
    The connectionManager is used to be able to address multiple databases at the same time. This is necessary reading the latest forum and comment entries from different databases on the start perspective's "latest entries" box.

  • filesystemHandler:
    The filesystemHandler is used to read the release folders and display the release and documentation files on the downloads page.

  • LinkHandler:
    The LinkHandler is applied to generate dynamic links. This is necessary within the search page.

  • FrontcontrollerLinkHandler:
    The FrontcontrollerLinkHandler generates the context sensitive links presented in the comments module at the end of (nearly) each documentation page.

  • Standard taglibs:
    Besides the framework components listet above a lot of standard taglibs are used to ease development. Among these are: <html:template />, <html:form />, <html:placeholder />.


7. Source code of the documentation page

The code of the documentation page can be obtained under http://media.adventure-php-fra...t-php4.zip. Please note, that the documentation page is served on a PHP 4 server. So if you like to run the page on your local development box, please be sure to have PHP 4 installed.

A current copy of the code can also be checked out from the sourceforge.net subversion repository. To do so, please call
APF-Template
svn co https://adventurephpfra.svn.sourceforge.net/svnroot/adventurephpfra/apfdocupage <your_target_folder>
on your favourite shell.


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.