Native PHP templating
Scheiden van buisiness logic en presentatie logic in PHP

Voor kleine projecten is het heel wel mogelijk PHP als scriptingtaal te gebruiken en alle code op één pagina te plaatsen. Daardoor treed er vermenging op van PHP-code en HTML, voor groter projecten is dit ongewenst om een aantal redenen :

  1. Functionaliteit is (achteraf) lastig te traceren
  2. Als er iets aan de layout gewijzigd moet worden is dit een tijdrovende klus
  3. Grotere kans op fouten
  4. Realisatie van een project binnen een team kan op problemen stuiten

Als model om de scheiding van buisiness logic en presentatie logic te realiseren in ee nuser interface is het Model-View-Presenter (MVP) pattern meer geschikt dan het MVC-pattern. Meer informatie



Native templating en de MVP-benadering

Het scheiden van de layout/content layer (Presenter/View) en de buisiness logic (Model) is aan te bevelen. Hiervoor is een template systeem heel geschikt. Ik was op zoek naar een lichtgewicht, snel en overzichtelijk template systeem, en heb gekozen voor het native templating van PHP zelf, omdat dit verreweg superieur is wat betreft snelheid en mogelijkheden.

  1. .phpm : PHP-classes waarin de functionaliteit ligt besloten [MODEL]
  2. .inc.php : PHP-code , als communicatie infrastructuur tussen model en view [PRESENTER]
  3. .php : PHP-code, waarin de structuur van de view wordt opgebouwd [VIEW /STRUCTURELAYER]
  4. .phtml : html-templates waarin PHP variabelen geparsed worden (content als plain text) [VIEW /CONTENTLAYER]
  5. .htm : bestanden met uitsluitend statische html-code (static content, metatags, headers etc)[CONTENT]

De verschillende extenties hangen samen met de verdeling van de taken binnen MVP :

  1. De .php files (VIEW/structuurlayer) mogen geen SQL-queries e.d. of HTML bevatten
  2. De .inc.php files (PRESENTER) mogen geen SQL-queries e.d. of HTML bevatten
  3. De .phpm files (Model (classes) / Datasources (SQL , SOAP, REST) ) mogen geen HTML bevatten
  4. De .phtml files (VIEW/contentlayer) mogen geen SQL-queries bevatten, en alleen simpele php loopstatements (for/if/while) en HTML strings
Het is aan te bevelen om de infrastructural presenter logic en de template code ieder in een aparte file onder te brengen, bijvoorbeeld template code in index.php en de presenter code als een soort "code-behind" file in presenter/index.inc.php


Ter illustratie hieronder eerst een voorbeeld van de "oude" manier van werken, dus alles in één file. Als de code relatief simpel blijft gaat het nog wel, maar bij een grote codebase met veel html-code en veel verschillende postbacks wordt alles snel onoverzichtelijk en lastig te onderhouden.

vb1 : Een voorbeeld van een opbouw in één file

<?php
//========This class is the model=============================
class DoSomething{
    //methods
    public function DataFromRequest(){};
    public function getDataFromDatabase(){};
    public function setDataToDatabase(){};
    public function exportDataObjectToLocalVariables($data){};
}

//=========Start of the Infrastructural Logic==================
$process = new DoSomething();

if(isset($_GET['submit'])){
    // do postback processing
    $processed_data = $process->DataFromRequest();
    //store data
    $process->setDataToDatabase( $processed_data );
    //store data locally
    $data = $processed_data;
} 
else{
    //get dataobject from database
    $data = $process->getDataFromDatabase();
}

//==========generate the View and Controller==================
function getHTML($data){
    exportDataObjectToLocalVariables($data);
    $html='
        //html code with local variables eg
        <span>'.$name.'</span>
        <input type="text" name="city" value="'.$city.'"/>
    ';
    return $html;

}

getHTML($data);

?>            
            
            

Verder uitgewerkt :



De .inc.php file is de ontvanger van alle GET- en POST requests, en bevat alle include/ require statements om de .phpm classes te laden die voor de dataverwerking zorgen.
  1. De GET en POST parameters worden verwerkt in de .inc.php file
    met behulp van de class-methods in de .phpm file die SQL-queries etc. uitvoeren.
  2. De .php file verwerkt de door de .inc.php aangeleverde dataobjecten,
    en stuurt het resultaat-object naar het template object dat geistantieerd is vanuit de .phtml file.
  3. De output van de .phtml file(s) objecten wordt dan opgeslagen in een overkoepelend template-object en als laatste gerenderd tot HTML-code die naar de browser wordt verzonden.

De diverse files kunnen in verschillende directories geplaatst worden om structuur op de server overzichtelijk te houden , bijvoorbeeld :

  1. De template engine class files in de model/class/ directory
  2. De .php files in de root
  3. De .inc.php files in de presenter/ directory
  4. De .phpm files in de model/ directory
  5. De .phtml in de view/ directory
  6. De .htm in de content/ directory
  7. De .util.php files in de include/ directory




vb1 : De template .phpm file



<?php

//gebaseerd op een script van http://www.massassi.com/php/articles/template_engines/

class Template {
    private $vars;

    /**
     * Constructor
     *
     * @param $file string the file name you want to load
     */
    function __construct($file = null) {
        $this->file = $file;
    }

    public function set($name, $value, $mode='') {
        
        switch($mode){
            case 'f' : //file
                $value = file_get_contents($value);
                break;
            default :
                //
                break;
        }
        $this->vars[$name] = is_object($value) ? $value->fetch() : $value;
    }

    public function fetch($file = null) {
        if(!$file) $file = $this->file;

        extract($this->vars);          // Extract the vars to local namespace
        ob_start();                    // Start output buffering
        include($file);                // Include the file
        $contents = ob_get_contents(); // Get the contents of the buffer
        ob_end_clean();                // End buffering and discard
        return $contents;              // Return the contents
    }
}

/**
 * An extension to Template that provides automatic caching of
 * template contents.
 */
class CachedTemplate extends Template {
    private $cache_id;
    private $expire;
    private $cached;

    /**
     * Constructor.
     *
     * @param $cache_id string unique cache identifier
     * @param $expire int number of seconds the cache will live
     */
    function __construct($cache_id = null, $expire = 900) {
    
        $this->cache_id = $cache_id ? 'cache/' . md5($cache_id) : $cache_id;
        $this->expire   = $expire;
    }

    public function is_cached() {
    
        if($this->cached) return true;
        if(!$this->cache_id) return false;
        if(!file_exists($this->cache_id)) return false;
        if(!($mtime = filemtime($this->cache_id))) return false;
        if(($mtime + $this->expire) < time()) {
            @unlink($this->cache_id);
            return false;
        }
        else {
            $this->cached = true;
            return true;
        }
    }
    
    public function fetch_cache($file) {
        if($this->is_cached()) {
            $fp = @fopen($this->cache_id, 'r');
            $contents = fread($fp, filesize($this->cache_id));
            fclose($fp);
            return $contents;
        }
        else {
            $contents = $this->fetch($file);
            if($fp = @fopen($this->cache_id, 'w')) {
                fwrite($fp, $contents);
                fclose($fp);
            }
            else {
                die('Unable to write cache.');
            }

            return $contents;
        }
    }
    
    public function clear_cache($cache_id=null){
        $cache_id = $cache_id ? 'cache/' . md5($cache_id) : $this->cache_id;
        @unlink($cache_id);
    }
    
    public function delete_cache($directory = 'cache/', $empty = true) { 
        if(substr($directory,-1) == "/") { 
            $directory = substr($directory,0,-1); 
        } 

        if(!file_exists($directory) || !is_dir($directory)) { 
            return false; 
        } elseif(!is_readable($directory)) { 
            return false; 
        } else { 
            $directoryHandle = opendir($directory); 
            
            while ($contents = readdir($directoryHandle)) { 
                if($contents != '.' && $contents != '..') { 
                    $path = $directory . "/" . $contents; 
                    
                    if(is_dir($path)) { 
                        deleteAll($path); 
                    } else { 
                        unlink($path); 
                    } 
                } 
            } 
            
            closedir($directoryHandle); 

            if($empty == false) { 
                if(!rmdir($directory)) { 
                    return false; 
                } 
            } 
            
            return true; 
        } 
    } 
}

?>

        



De index.php file is de VIEW structuurlaag waarin de elementen waaruit de pagina is opgebouwd met behulp van de template-class worden geïnitialiseerd.

vb1 : Voorbeeld van de index.php Infrastructural Logic file

       
<?php
require_once('../model/class.template.phpm');

//templates
$tpl  = new Template('view/index.phtml'); // this is the outer template
$head = new Template('../view/head.phtml'); // this is the head template
$form = new Template('view/form.phtml'); // this is the form template
$footer = new Template('view/footer.phtml'); // this is the form template

//load content
$tpl->set('doctype', '../content/doctype.htm', 'f');

$head->set('title', 'Aaldert van Weelden formtemplate');
$head->set('meta', 'content/meta.htm', 'f');
$head->set('css', 'content/css.htm', 'f');
$head->set('script', 'content/script.htm', 'f');

$tpl->set('head', $head);
$tpl->set('title', 'Form Template Engine');

/*
 * build formvalues from database or superglobal POST array and handle postvars here
 */
$data=array('naam'=>' Aaldert','adres'=>' Platvoetsdijk 28');//example


$form->set('post', $data);

$footer->set('footer', '');

$tpl->set('buttonbar', '');
$tpl->set('form', $form);
$tpl->set('footer', $footer);

//fetch filled template
echo $tpl->fetch('view/index.phtml');
?>
        


vb1 : Voorbeeld van een index.phtml template file

       
<?php
$w='
'.$doctype.'
    '.$head.'
    <body>
        <h1>'.$title.'</h1>
        '.$buttonbar.'
        '.$form.'
        
        '.$footer.'
    </body>
</html>
';
echo $w;
?>
        


vb1 : Voorbeeld van de head.phtml file

       
<?php
$w='
<head>
    <title>'.$title.'</title>
    
'.$meta.'
	
'.$css.'
	
'.$script.'
		
</head>
';
echo $w;
?>
        


vb1 : Voorbeeld van de script.htm file (content)

       
<script type="text/javascript"src="../../../JAVASCRIPT/js/utils/util.js"></script>
<script type="text/javascript"src="../../../JAVASCRIPT/js/utils/request.js"></script>
<script type="text/javascript"src="../../../JAVASCRIPT/js/utils/events.js"></script>
<script type="text/javascript"src="../../../JAVASCRIPT/js/jquery/jquery.js"></script>
	
<script type="text/javascript"src="js/lab.js"></script>
        


Voorbeeld van directory structuur op de server



Caching

De template-class bevat nog een caching functionaliteit die pagina's die veel databaseverkeer veroorzaken op de server kan opslaan, zodat de laadtijd van de pagina korter en de serverload kleiner wordt


Een voorbeeld van een toepassing volgt hieronder, de code kunt u hier downloaden: Template example Voer een aantal verschillende waarden in voor de redirect parameter en laad de pagina om de caching functie in werking te zien.