Template-Klasse: Verbesserungsvorschläge

mAu

Erfahrenes Mitglied
Hallo Folks. Da ich nun meine Templateklasse vollendet habe, wollte ich sie hier mal auf den Prüfstand geben. Ich habe versucht sie durch Kommentare verständlicher zu machen, was mir glaube ich nicht gelungen ist :) Aber vielleicht blickt ihr ja trotzdem durch. WIe gesagt, ich würde gerne Kritik / Verbesserungsvorschläge und Anregungen hören, Lob natürliuch auch haha, wenn es denn was zu loben gibt :) Vielleicht ist sie ja auch was für die PHP-Codeschnipsel Abteilung.
Zu beachten ist, dass die Klasse nur für PHP5 geschrieben ist!

Hier mal die Klasse:
class.template.php
PHP:
<?php
error_reporting(E_ALL); /* Kann bei Bedarf durch Auskommentieren mit # deaktiviert werden */
/*
 *
 *  sTemplate v 0.0.0.1 - Kleine Templateklasse
 *  Copyright (C) 2006  Maurício Hanika
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation;
 *	either version 2 of the License, or (at your option) any later version.
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *	See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with this program;
 *	if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 *
 * 	Kleine Templateklasse, die verschachtelte Bloecke und if-else Anweisungen unterstuetzt. Desweiteren
 *	sind includes anderer Dateien in ein Template moeglich. Fuer ein Anwendungsbeispiel einfach in den
 *	Ordner gucken ;)
 *	Beachte bitte, dass wenn du einen if-Block definierst, ein {else} nicht doppelt vorkommen darf,
 *	also folgendes waere falsch:
 *	 {if:einBlock}
 *		Kondition ist wahr!
 *		 {else}Error {else}Kondition ist falsch!
 *	 {endif:einBlock}
 *	Folgendes wuerde geparsed werden, wenn eine falsche Aussage vorliegt: 'Error {else}Kondition ist falsch!'.
 *				
 *	Wenn ihr verschachtelte Bloecke verwendet, beachtet bitte, dass bei der Zuweisung die Kind-Bloecke jeweils
 *	direkt nach ihrem Eltern-Block definiert werden, also:
 *	 $tpl->setBlock('Liste1', array('list' => 'Punkt1'));
 *	 $tpl->setBlock('Liste1.Child1', array('child' => 'Punkt 1.1'));	
 *
 *	 $tpl->setBlock('Liste1', array('list' => 'Punkt2'));
 *	 $tpl->setBlock('Liste1.Child1', array('child' => 'Punkt 2.1'));	
 *					
 *	Folgendes wuerde nicht funktionieren, bzw. nicht den gewuenschten Effekt haben:
 *	 $tpl->setBlock('Liste1', array('list' => 'Punkt1'));
 *	 $tpl->setBlock('Liste1', array('list' => 'Punkt2'));
 *
 *	 $tpl->setBlock('Liste1.Child1', array('child' => 'Punkt 1.1'));	
 *	 $tpl->setBlock('Liste1.Child1', array('child' => 'Punkt 2.1'));	
 *	Der Parser wuesste nicht wozu die jeweiligen Unterpunkte gehoeren, bzw. er wuerde beide Unterpunkte 'Punkt2'
 *	unterordnen, da dieser zuvor definiert wurde.
 *	
 *  Changelog
 *		-v 0.0.0.2
 *			+ Kleine Code Optimierung bei der Verarbeitung von verschachtelten Bloecken
 *			+ Unsinnige chkphp() Funtion herausgenommen, da diese Klasse nur unter PHP5 laeuft
 *
 *
 *  @name:		sTemplate
 *  @version:	0.0.0.2
 *  @author:	Maurício Hanika
 *  @copyright: Maurício Hanika
*/


/* Konfiguration, bitte nur aendern, wenn du weißt was du machst ;) */
define('TPL_DELIMITER_START', '{');		// Variablenbegrenzer vorn   
define('TPL_DELIMITER_END', '}');		// Variablenbegrenzer hinten

class Template{

		protected $data		= '';		// Array mit eingelesenem Templatecode
		protected $vars		= array();	// Array mit den zu ersetzenden Variablen
		protected $blocks	= array();	// Array mit dynamischen Bloecken
		protected $ifBlocks = array();	// Array mit if-else Bloecken
	
	/*
	 * @name: __construct
	 * @desc: Konstruktor, ruft setTemplate auf
	 */
	function __construct($data, $isString=false){
	  
	  $this->setTemplate($data, $isString);
	  
	}

	/*
	 * @name: setTemplate
	 * @desc: Laedt ein Templatefile in $this->data, ist $isString auf true gesetzt und $data ein String, dann wird dieser anstelle einer Datei verwendet
	 */	
	private function setTemplate($data, $isString){

	  if($isString == false && file_exists($data)){
	        
		  $data = file_get_contents($data);
	  	
	  }
	
	$this->data = (string)$data; 
	  
	}
	
	/*
	 * @name: setVar
	 * @desc: Setzt einem Placer {$search} zugehoerige Variable $replace
	 */	
	public function setVar($search, $replace){
	  
	  if(!empty($search))
	  
		$this->vars[$search] = (string)$replace;
	  
	}
	
	/*
	 * @name: setArray
	 * @desc: Wie setVar, nur dass ein Array verarbeitet wird (Array('Key1' => $key1, 'Key2' => $key2, ...))
	 */	
	public function setArray($array){
	  
	  foreach($array as $search => $replace){
	    
	    if(!is_array($replace))
	    
		 $this->setVar($search, $replace);
		 
	    else /* Wird als Wert fuer $search ein Array gefunden, handelt es sich vermutlich um einen verschachtelten Block, oder um einen Fehler */
	    
		  if(preg_match('/\{loop:'.$search.'\}(.*)\{endloop:'.$search.'\}/siU', $this->data, $block)){ /* Wird ein Block gefunden, parse ihn */

		 	$this->replaceBlock($search, $replace);
		 	
		 } else{ /* Kein Block? Dann war's wohl ein Fehler */
		   
		 	trigger_error('Wert von <strong>{'.$search.'}</strong> ist ein Array. <strong>{'.$search.'}</strong> kann jedoch kein Array sein', E_USER_NOTICE); 
		  
		 }  
		 
	  }
	  
	}
	
	/*
	 * @name: setBlockVar
	 * @desc: Setzt Variablen, die einem Block angehören.
	 */	
	public function setBlockVar($bName, $array){
	  
	  if(strstr($bName, '.')){ /* Handelt es sich um einen verschachtelten Block? */
	   
		$bName   = explode('.', $bName);

		$str = '';
		
                for ($i = 0; $i < count($bName); $i++) /* Danke fuer die Anregung an Fanste von tutorials.de - Hier wird aus einem Sting (z.B. Block1.Block1-1.Block1-1-1) ein mehrdimensionales Array gemacht */
                {
                    $str .= '[\'' . $bName[$i] . '\']';

					eval('$lastiteration = (!isset($this->blocks' . $str . ')) ? -1 : count($this->blocks' . $str . ') - 1;');
				
                    $str .= '[' . (($lastiteration < 0) ? 0 : (($i == count($bName) - 1) ? $lastiteration + 1 : $lastiteration)) . ']'; /* Damit die Kindbloecke sich nicht ueberschreiben, zaehlen und Anzahl der Bloecke um eins erhoehen, also z.B. Kindblock[3] = array(...) */
                }
        
        eval('$this->blocks'.$str.' = $array;'); /* Sollte jemand einen Weg kennen, ohne eval() ein mehrdimensionales Array zu machen, waere ich fuer einen Hinweis sehr dankbar! */
		
	  } else{ /* Nein, es liegt keine Verschachtelung vor */
	    
	  	if(!isset($this->blocks[$bName]))
	  
	  		$this->blocks[$bName] = array();	    
	    
	  	$bSize = count($this->blocks[$bName]);
	  
	  	if(!empty($bName) && is_array($array))
	  
	  		$this->blocks[$bName][$bSize] = $array;	 
		     
	  }
	  	
	}
	
	/*
	 * @name: setIfBlock
	 * @desc: Setzt einem if-Block zugehoerige Kondition $condition
	 */	
	public function setIfBlock($ifBlock, $condition){
	  
	  if(!empty($ifBlock))
	  
		$this->ifBlocks[$ifBlock] = $condition;
	  
	}
	
}
?>

class.template.parser.php
PHP:
<?php
error_reporting(E_ALL); /* Kann bei Bedarf durch Auskommentieren mit # deaktiviert werden */
/*
 *
 *  sTemplate v 0.0.0.1 - Kleine Templateklasse
 *  Copyright (C) 2006  Maurício Hanika
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation;
 *	either version 2 of the License, or (at your option) any later version.
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *	See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with this program;
 *	if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 *
 * 	Kleine Templateklasse, die verschachtelte Bloecke und if-else Anweisungen unterstuetzt. Desweiteren
 *	sind includes anderer Dateien in ein Template moeglich. Fuer ein Anwendungsbeispiel einfach in den
 *	Ordner gucken ;)
 *	Beachte bitte, dass wenn du einen if-Block definierst, ein {else} nicht doppelt vorkommen darf,
 *	also folgendes waere falsch:
 *	 {if:einBlock}
 *		Kondition ist wahr!
 *		 {else}Error {else}Kondition ist falsch!
 *	 {endif:einBlock}
 *	Folgendes wuerde geparsed werden, wenn eine falsche Aussage vorliegt: 'Error {else}Kondition ist falsch!'.
 *				
 *	Wenn ihr verschachtelte Bloecke verwendet, beachtet bitte, dass bei der Zuweisung die Kind-Bloecke jeweils
 *	direkt nach ihrem Eltern-Block definiert werden, also:
 *	 $tpl->setBlock('Liste1', array('list' => 'Punkt1'));
 *	 $tpl->setBlock('Liste1.Child1', array('child' => 'Punkt 1.1'));	
 *
 *	 $tpl->setBlock('Liste1', array('list' => 'Punkt2'));
 *	 $tpl->setBlock('Liste1.Child1', array('child' => 'Punkt 2.1'));	
 *					
 *	Folgendes wuerde nicht funktionieren, bzw. nicht den gewuenschten Effekt haben:
 *	 $tpl->setBlock('Liste1', array('list' => 'Punkt1'));
 *	 $tpl->setBlock('Liste1', array('list' => 'Punkt2'));
 *
 *	 $tpl->setBlock('Liste1.Child1', array('child' => 'Punkt 1.1'));	
 *	 $tpl->setBlock('Liste1.Child1', array('child' => 'Punkt 2.1'));	
 *	Der Parser wuesste nicht wozu die jeweiligen Unterpunkte gehoeren, bzw. er wuerde beide Unterpunkte 'Punkt2'
 *	unterordnen, da dieser zuvor definiert wurde.
 *
 *  Changelog
 *		-v 0.0.0.2
 *			+ Kleine Code Optimierung bei der Verarbeitung von verschachtelten Bloecken
 *			+ Unsinnige chkphp() Funtion herausgenommen, da diese Klasse nur unter PHP5 laeuft
 *
 *
 *  @name:		sTemplate
 *  @version:	0.0.0.2
 *  @author:	Maurício Hanika
 *  @copyright: Maurício Hanika
*/
require_once ('class.template.php');

class Parser extends Template{

	/*
	 * @name: doIf
	 * @desc: Verarbeitet If-Bloecke
	 */	  
  	private function doIf($ifBlock, $content){
	   
	  if(isset($this->ifBlocks[$ifBlock])){
	    
	    if($this->ifBlocks[$ifBlock])
	      
		  return (stristr($content, '{else}')) ? substr($content, 0, strpos($content, '{else}')) : $content;
		
		else{
		  
		  if(stristr($content, '{else}')){
		    
		    return substr($content, stripos($content, '{else}') + 6);
		    
		  }
		  
		}
	    
	  } else{
	    
	    trigger_error('Zugehörige Kondition zum If-Block <strong>'.$ifBlock.'</strong> wurde nicht gefunden ', E_USER_WARNING);   
	    
	    return false;
	    
	  }
	    
	}

	/*
	 * @name: replaceVars
	 * @desc: Ersetzt Platzhalter im Template durch Variablen
	 */	
	private function replaceVars($clearTpl){
	  
	  /* Replace the defined variables */
	  foreach ($this->vars as $search => $replace){
	    	
	  		$this->data = str_replace(TPL_DELIMITER_START.$search.TPL_DELIMITER_END, $replace, $this->data); 
	    
	  }
	  
	  /* if ($clearTpl === true)
			
			$this->data = preg_replace('/<!--Begin:([a-zA-Z0-9]+)-->(.+)<!--End:\1-->/siU', '', $this->data);
			Ich suche noch eine gute Loesung hierfuer. Bei Ideen, e-Mail an: tickduck@gmx.de */
	  
	  return $this->data;
	  
	} 

	/*
	 * @name: replaceBlock
	 * @desc: Ersetzt (verschachtelte) Bloecke in den Templates
	 */		
	protected function replaceBlock($bName, $array){
	  
	  preg_match('/\{loop:'.$bName.'\}(.*)\{endloop:'.$bName.'\}/siU', $this->data, $block);
	  
	  	$tmp = '';
		foreach($array as $tmpArray){
	        
			$obj = new Parser($block[1], true);
		  
			$obj->setArray($tmpArray); 
		  
			$tmp .= $obj->parse(true);
		  	
		}
			
		$this->data = preg_replace('/\{loop:'.$bName.'\}(.*)\{endloop:'.$bName.'\}/siU', $tmp, $this->data);			
		  
	}
	
	/*
	 * @name: parseIncludes
	 * @desc: Dateien werden eingebunden
	 */	
	private function parseIncludes(){	  
	  
	  while(preg_match('/\{include:(.+)\}/siU', $this->data)){
	    
		$this->data = preg_replace('/\{include:(.+)\}/esiU', "implode('', file('\\1'))", $this->data);
	   
	  }	  
	  
	}

	/*
	 * @name: parseIf
	 * @desc: Holt die If-Bloecke aus den Templates und schickt sie zur Verarbeitung an die doIf-Funktion
	 */		
	private function parseIf($ifBlock='', $condition=''){

	  while(preg_match('/\{if:([a-zA-Z0-9]+)\}(.*)\{endif:\1\}/siU', $this->data)){

	    $this->data = preg_replace('/\{if:([a-zA-Z0-9]+)\}(.*)\{endif:\1\}/esiU', '$this->doIf("\\1","\\2")', $this->data); 
	    
	  }
	  
	}
	
	/*
	 * @name: show
	 * @desc: Ausgabe der $this-Variable, nur zu entwicklungstechnischem Zweck
	 */	
	function show(){
	  
	  echo '<pre>';
	   print_r($this);
	  echo '</pre>';
	  
	}
  
	/*
	 * @name: parse
	 * @desc: Alles fertig? Dann das Template parsen
	 */	
	function parse($noParse=true, $parseIncludes=true, $clearTpl=true){
	  
	  if($noParse === false){
	     
	    foreach ($this->blocks as $bName => $array){
		  
			$this->replaceBlock($bName, $array);
		  	
		}	
		
		$this->parseIf();    
	    
	  }
	  
	  if($clearTpl === true){
	    
		// Muss noch implentiert werden (soll Kommentare in Templates, etc. entfernen)
	    
	  }
	  
	  if($parseIncludes === true)
	  
	  	$this->parseIncludes();

	  return $this->replaceVars($clearTpl);
	  
	}  
}
?>

index.php, Beispielseite
PHP:
<?php
/*
 *  sTemplate v 0.0.0.1 - Kleine Templateklasse
 *  Copyright (C) 2006  Maurício Hanika
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation;
 *	either version 2 of the License, or (at your option) any later version.
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *	See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with this program;
 *	if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 *
 *
 *  @name:		sTemplate
 *  @version:	0.0.0.1
 *  @author:	Maurício Hanika
 *  @copyright: Maurício Hanika
 *	@desc:		Beispielanwendung fuer die sTemplate-Klasse
*/

require_once('class.template.parser.php');

$tpl = new Parser('index.htm'); # Fuer weitere Templates jeweils ein neues Objekt erstellen.

# Variablen zuweisen #
$tpl->setVar('title', 'Beispielseite');
$tpl->setVar('class', 'sTemplate');
$tpl->setVar('name', 'Maurício Hanika');
$tpl->setVar('version', '0.0.0.2');

# Loop zuweisen. Bitte beachtet, dass Bloecke keinen . im Namen enthalten, ausser es handelt sich um einen Kindblock (z.B. Block1.Block1-1.Block1-1-1) #
for($i=0; $i < 10; $i++){
  $tpl->setBlockVar('count', array('count' 	=> $i,
  								   'text' 	=> ' ist der aktuelle Wert von $i'));
  $tpl->setBlockVar('count.level', array('lvl' => '<div style="padding-left: '.(2 * $i).'px;">', 'lvlE' => '</div>'));
}

# If-Else Block #
$tpl->setIfBlock('tag', (date('G') > 12 && date('G') < 18));
$tpl->setIfBlock('abend', (date('G') > 18 && date('G') < 24));

echo $tpl->parse(false /* Wenn true, dann werden nur Variablen ersetzt und Bloecke und if-Abfragen werden ignoriert */,
				 true /* Per {include:} zugewiesene Dateien einbinden */,
				 true/* Hat noch keine Bewandnis */);
?>

index.htm, Beispieltemplate
HTML:
<html>
<head>
<title>{title}</title>
<style>
<!--
body, tr, td{
  font-family: 	verdana, arial, sans-serif;
  font-size:	10px;
}
-->
</style>
</head>
<body>
<h1>{title}</h1>
<strong>{class}</strong> wurde von <strong>{name}</strong> entworfen und liegt in der <strong>Version {version}</strong> vor.
<br />
<br />
Loop Beispiel:<br />
<table border="1" >
{loop:count}
<tr>
 <td>{loop:level}{lvl} Level {count}. {count}{text}{lvlE}{endloop:level}</td>
</tr>
{endloop:count}
</table>
<br />
If-Else Block:<br />
<strong>
{if:tag}
Guten Tag!{else}
  {if:abend}
	Guten Abend!{else}
		Guten Morgen!
  {endif:abend}
{endif:tag}
</strong>
<br />
<br />
Include (message.html):<br />
{include:message.html}
</body>
</html>
 
Auf den ersten Blick sieht es schick aus, nur leider ist das für mich etwas zu hoch! Habe mich bisher noch nicht in PHP 5 eingearbeitet und mit so eine Templete-Klasse habe ich auch noch nie gearbeitet...

Aber wenn ich Zeit habe werde ich sie bestimmt mal offline testen und zerlegen und analysieren, schließlich lernt man so am besten!

Ich würde es sehr gerne in den Codeschnippsel sehen, hier wird es sonst unter gehen.

Gute Arbeit!
 
Naja leider ist die Klasse nur begrenzt (wegen PHP5) einsetzbar.
Musste erst kürzlich so eine ähnliche Klasse PHP4-Tauglich umschreiben :-(
 
Ich wollte / will mich mit dieser Klasse etwas in OOP einarbeiten. Und da man ja dauernd liest, dass das von PHP unterstützte OOP erst seit PHP5 "richtiges" OOP ist, wollte ich gleich da einsteigen. Es ist schon beabsichtigt, dass sie nur unter PHP5 läuft.
 
Wie wäre es wenn man mehrere Templatedateien einlesen kann.

Also zum Beispiel

Datei1 -> Header
Datei2 -> Body
Datei3 -> Footer
 
Zurück