Je pensais que tu avais déjà utilisé un moteur de template, c'est pourquoi je n'ai pas commandé mon code plus que ça.
Un moteur de template va simplement gérer des "vues" (cf. modèle MVC) :
- on instancie le moteur
- on lui fournit des variables
- on lui donne la vue à gérer
- il nous donne le résultat
En reprenant le code que je t'ai donné, tu as :
- le moteur de template, c'est la classe Template que j'ai donnée.
- le template (ou vue), c'est le code que j'ai fourni avec les foreach/endforeach & co (que tu vas par exemple enregistrer en "montemplate.tpl", le fichier n'a pas besoin de se nommer .php pour fonctionner).
L'utilisation sera de ce genre, très classique et conforme à la plupart des moteurs existant :
// Traitements, définitions de mes variables
$animaux = array(
array('nom'=>'Tigre'),
array('nom'=>'Lion'),
array('nom'=>'Panthère')
);
// Instanciation de mon moteur
$template = new Template;
// Passage des variables au moteur
$template->set('animaux', $animaux);
// Affichage du résultat
$template->render('montemplate.tpl');
Crois-moi, c'est une excellente base de départ.
En reprenant exactement le même principe, et en ajoutant quelques idées d'options, on arrive vite à un système beaucoup plus complet. Tiens si tu veux j'ai retrouvé une version "à trous" du moteur que j'utilise personnellement pour les petits projets (pour les gros j'utilise smarty parce que sinon ça défrise les graphistes de voir des balises PHP) :
<?php
class Template {
/**
* Variables passées à la vue
*/
protected $vars = array ();
/**
* Options du moteur
*/
protected $options = array (
'template_dir' => null, // Dossier où aller chercher les vues (ne pas oublier le slash de fin, exemple : '/path/to/templates/')
'cache' => false, // TODO : Activer le cache ?
'cache_dir' => 'cache', // TODO : Dossier de stockage des fichiers de cache
'cache_timeout' => 3600, // TODO : Expiration des fichiers de cache
'content_type' => 'text/html', // Type MIME de la page
'charset' => null, // Encodage de la page (il est conseillé de le définir)
'compress' => false, // TODO : Activer la compression Gzip
'automatic_headers' => true // Envoyer automatiquement les entêtes à l'appel de render()
);
/**
* Entêtes HTTP définies
*/
protected $headers = array ();
/**
* Dernière instance créée
*/
protected static $lastInstance = null;
/**
* Constructeur
*/
public function __construct($options = array ()) {
$this->options = array_merge($this->options, $options);
$this->checkOptions();
self :: $lastInstance = $this;
}
/**
* @todo Template::checkOptions()
*/
protected function checkOptions() {
}
/**
* Rappelle la dernière instance créée. Ce n'est pas tout-à-fait du singleton, mais permet de l'utiliser
* comme tel selon l'application.
*/
public static function instance() {
return self :: $lastInstance;
}
/**
* Valeur d'une option
*/
public function getOption($option, $default = null) {
$value = @ $this->options[$option];
if (is_null($value)) {
return $default;
} else {
return $value;
}
}
/**
* Redéfinit une option
*/
public function setOption($option, $value) {
$this->options[$option] = $value;
}
/**
* Renvoie la liste des noms des variables passées
*/
public function getVars() {
return array_keys($this->vars);
}
/**
* Passe une variable à la vue
*/
public function set($var, $val) {
$this->vars[$var] = $val;
}
/**
* Récupère la valeur d'une variable passée à la vue
*/
public function get($var, $default = null) {
return isset ($this->vars[$var]) ? $this->vars[$var] : $default;
}
/**
* Renvoie la liste des entêtes HTTP (inclut le content-type défini via les options)
*/
public function getHeaders() {
$headers = $this->headers();
$content_type = $this->getOption('content_type', 'text/html');
if ($charset = $this->getOption('charset')) {
$content_type .= '; charset=' . $charset;
}
$headers['Content-Type'] = $content_type;
return $headers;
}
/**
* Ajoute une entête HTTP
*/
public function addHeader($header, $value) {
if (strtolower($header) == 'content-type') {
list ($mime, $charset) = explode(';', $value, 2);
$this->setOption('content_type', trim($mime));
$this->setOption('charset', trim($charset));
} else {
$this->headers[$header] = $value;
}
}
/**
* Envoie les entêtes HTTP (nécessaire si vous avez désactivé l'option 'automatic_headers')
*/
public function sendHeaders() {
$headers = $this->getHeaders();
foreach ($headers as $header => $value) {
header($header . ': ' . $value);
}
}
/**
* @todo Template::isCached()
*/
protected function isCached($template, $cache_key = null) {
return false;
}
/**
* @todo Template::cache()
*/
protected function doCache($template, $cache_key = null) {
}
/**
* @todo Template::getFromCache()
*/
protected function getFromCache($template, $cache_key = null) {
}
/**
* Affiche le résultat de l'injection des variables passées dans la vue donnée
* Si l'option 'automatic_headers' n'est pas désactivée, les entêtes sont envoyées
*/
public function render($template, $cache_key = null) {
if ($this->getOption('automatic_headers')) {
$this->sendHeaders();
}
$result = $this->fetch($template, $cache_key);
if (false === $result) {
return false;
} else {
echo $result;
return true;
}
}
/**
* Renvoie le résultat de l'injection des variables passées dans la vue donnée
* N'envoie aucune entête
*/
public function fetch($template, $cache_key = null) {
if ($dir = $this->getOption('template_dir')) {
$template = $dir . $template;
}
if (!is_file($template)) {
return false;
}
$cache_enabled = $this->getOption('cache');
$get_from_cache = $cache_enabled && $this->isCached($template, $cache_key);
if ($get_from_cache) {
return $this->getFromCache($template, $cache_key);
} else {
extract($this->vars);
ob_start();
include $template;
$contents = ob_get_contents();
ob_end_clean();
if ($cache_enabled) {
$this->doCache($template, $cache_key);
}
return $contents;
}
}
}
?>
Il n'y a que la base qui fonctionne, reste à remplir les trous pour activer le support du cache, de la compression Gzip, la gestion des erreurs, et toutes les fonctionnalités qui pourraient te passer par la tête

Il est bon de noter que dans le template (le fichier .tpl) on est également dans la méthode Template::fetch(), ce qui signifie que l'on a accès à... $this

C'est très intéressant parce que ça veut dire que :
- on peut récupérer les options avec $this->getOption().
- on peut récupérer les headers de la même façon.
- on a accès aux variables $template et $cache_key (si elles n'ont pas été écrasées par extract($this->vars), auquel cas on pourra faire suivre l'extract() d'un $func_args = func_get_args() pour en récupérer l'accès).
- on peut tout à fait définir des "fonctions spécial template" sous forme de méthode de la classe Template que l'on pourra simplement appeler $this->maFonction().