$title = 'My title';
include('templates/index.html');
<html><head><title><?php echo $title ?></title></head>
<!– … –>
То есть, переменную определили и приинклюдили html-файл. Я считаю, что это в корне неверный подход. Почему?
Во-первых, все переменные, переданные в шаблон, должны храниться в одном месте (свойстве класса шаблонизатора).
Во-вторых, в шаблонизаторе не должно быть доступа к переменным, которые в него не переданы, и к функциям, которые в нем не определены.
В-третьих, должен быть определен набор функций, необходимых для работы.
Таким образом, я пришел к выводу, что шаблонизатор нужен, но он не должен быть навороченным тормозом типа Smarty.
Идеология блочных шаблонизаторов (XTemplate, например) мне не импонирует потому, что в них нет ветвлений как таковых, есть только циклы.
Потому я написал свой.
UPD. Далее идет код старой версии, новая версия здесь: http://pyha.ru/forum/topic/1402.msg47325#msg47325
Начнем с того, что нам нужно разобраться с обработкой ошибок. Я использую для этой цели исключения, потому определяем класс исключений:
class STempException extends Exception {}
Тут нам больше ничего не нужно. Переходим к самому шаблонизатору:
class STemp
{
/**
* The name of the directory where templates are located.
*
* @var string.
* @access private.
*/
private $path;
/**
* Name of the template.
*
* @var string.
* @access private.
*/
private $template;
/**
* Where assigned template vars are kept.
*
* @var array.
* @access private.
*/
private $variables = array();
/**
* Parameters of the template engine.
*
* @var array.
* @access private
*/
private $params = array(
'xss_protection' => true,
'exit_after_display' => true,
'endofline_to_br' => false
);
/**
* File that include in template.
*
* @var string.
* @access private.
*/
private $include_file;
/**
* The class constructor. Set name of the directory where templates are located.
*
* @param string $path name of the directory where templates are located, default 'templates/'.
* @access public.
*/
public function __construct($path = 'templates/')
{
$this->path = $path;
}
/**
* Set parameters of template engine.
*
* @param string $param name of the parameter.
* @param bool $value value of the parameter.
* @return bool TRUE if parameter set, FALSE if didn't set.
* @access public.
*/
public function setParam($param, $value)
{
if (isset($this->params[$param])) {
$this->params[$param] = $value;
return true;
}
return false;
}
/**
*
* @param string $include_file path to include file.
* @access public.
*/
public function setIncludeFile($include_file)
{
$this->include_file = $this->path.$include_file;
if (!file_exists($this->path.$include_file))
throw new STempException('Include file '.$this->include_file.' not exitst');
}
/**
* Assigns values to template variables.
*
* @param string $name the template variable name.
* @param mixed $value the value to assign.
* @access public.
*/
public function assign($name, $value)
{
$this->variables[$name] = $value;
}
/**
* Executes and displays the template results.
*
* @param string $template the template name.
* @access public.
*/
public function display($template)
{
$this->template = $this->path.$template;
if (!file_exists($this->template))
throw new STempException('Template file '.$template.' not exitst');
require_once($this->template);
if ($this->params['exit_after_display'])
exit;
}
/**
* Get value of template variable.
*
* @param string $name the template variable name.
* @return mixed value of template variable with this name. FALSE if variable not set.
* @access private.
*/
private function __get($name)
{
if (isset($this->variables[$name])) {
$variable = $this->variables[$name];
if ($this->params['xss_protection'])
$variable = $this->xssProtection($variable);
if ($this->params['endofline_to_br'])
$variable = $this->endoflineToBr($variable);
return $variable;
}
return false;
}
/**
* Include file
*
* @access private
*/
private function includeFile()
{
if (!file_exists($this->include_file))
throw new STempException('Include file '.$this->include_file.' not found');
require_once($this->include_file);
}
/**
* For the formation of endings of words.
*
* @param int $value number.
* @param string $word0 word in the singular.
* @param string $word1 word in the plural (2, 3).
* @param string $word1 word in the plural.
* @param string $separator separator, default ' '.
* @return string formed words
* @access private.
*/
private function morph($value, $word0, $word1, $word2, $separator = ' ')
{
if (preg_match('/1\d$/', $value))
return $value.$separator.$word2;
elseif (preg_match('/1$/', $value))
return $value.$separator.$word0;
elseif (preg_match('/(2|3|4)$/', $value))
return $value.$separator.$word1;
else
return $value.$separator.$word2;
}
/**
* For protection from XSS.
*
* @param mixed $variable data for protection.
* @return mixed protected data.
* @access private.
*/
private function xssProtection($variable)
{
if (is_array($variable)) {
$protected = array();
foreach ($variable as $key=>$value)
$protected[$key] = $this->xssProtection($value);
return $protected;
}
return htmlspecialchars($variable);
}
/**
* Inserts HTML line breaks before all newlines in a string.
*
* @param mixed $variable data for protection.
* @return mixed data where string with <br /> inserted before all newlines.
* @access private.
*/
private function endoflineToBr($variable)
{
if (is_array($variable)) {
$protected = array();
foreach ($variable as $key=>$value)
$protected[$key] = $this->endoflineToBr($value);
return $protected;
}
return nl2br($variable);
}
}
В конструкторе мы можем указать путь к директории шаблонов (по умолчанию temlates/).
С помощью метода setParam мы можем установить параметры шаблонизатора. Их всего три (мне этого достаточно, при необходимости можно добавлять параметры). Первый параметр — xss_protection — как понятно из названия, нужен для защиты от уязвимости xss. Если значение параметра установлено как true, все переменные, которые мы используем в шаблоне, перед отдачей автоматически обрабатываются функцией htmlspecialchars (в том числе элементы массивов). Второй параметр — exit_after_display — нужен для того, чтобы, при потребности, мы могли остановить выполнение сценария после отображения шаблона. Третий параметр — endofline_to_br — обрабатывает все переменные перед отдачей (в том числе элементы массивов) функцией nl2br.
Методом setIncludeFile мы можем установить подключаемый шаблон. Очень часто используется общий шаблон index.tpl.php и в него, в зависимости от условий, подключают изменямую часть. Вот для автоматизации данного процесса и нужен этот метод. Если подключаемый файл не существует, выбрасыватся исключение.
Метод assign служит для передачи переменных в шаблон.
Метод display отображает шаблон. Если файл шаблона не существует, выбрасывается исключение. Если параметр exit_after_display установлен как true, этот метод также завершает работу сценария (практически всегда отображение шаблона является последним действием).
«Магический» метод __get возвращает значение переданной в шаблон переменной. Если переменная не определена, возвращает false. В зависимости от параметров, переменные перед отдачей могут обрабатываться.
Метод includeFile инклюдит файл, назначенный методом setIncludeFile и выбрасывает исключение, если этот файл не найден.
Метод morph, не совсем «шаблонизаторный», служит для формирования правильных окончание слов, относящихся к числительным. То есть, 1 комментарий, 2 комментария, 5 комментариев. В метод нужно передать само число, три разных варианта и, опционально, разделитель слов (по умолчанию неразрывный пробел).
Метод xssProtection обрабатывает данные функцией htmlspecialchars. Если на входе массив, то он рекурсивно перебирается и обрабатываются все его элементы.
Метод endoflineToBr обрабатывает данные функцией nl2br. Если на входе массив, то он, как и в предыдущем методе, рекурсивно перебирается и обрабатываются все его элементы.
Как это выглядит на практике? Предположим, нам нужно распечатать статью и комментарии к ней. Данные по статье в массиве $article, комменты — в $comments.
Контроллер:
$stemp = new STemp();
$stemp->assign("article", $article);
$stemp->assign("comments", $comments);
try {
$stemp->setIncludeFile("article.tpl.php");
$stemp->display("index.tpl.php");
} catch (STempException $e) {
die('STemp error: '.$e->getMessage());
}
Шаблон index.tpl.php:
<html>
<head>
<title><?php echo $this->title ?></title>
</head>
<body>
<?php $this->includeFile() ?>
</body>
</html>
Шаблон article.tpl.php:
<h1><?php echo $this->article['title'] ?></h1>
<?php $this->setParam('xss_protection', false); $this->setParam('endofline_to_br', true) ?>
<div class="content">
<?php echo $this->article['content'] ?>
</div>
<p><?php echo $this->morph(count($this->comments), 'комментарий', 'комментария', 'комментариев') ?>:</p>
<?php $this->setParam('xss_protecttion', true) ?>
<?php foreach ($this->comments as $key=>$value) { ?>
<p class="user"><?php echo $value['username'] ?>:</p>
<p class="comment"><?php echo $value['text'] ?></p>
<?php } ?>
Скачать шаблонизатор
Использование класса в личных нуждах разрешено без ограничений. При перепечатке статьи или исходного кода, в том числе, частично, ссылка на меня (на мой сайт, посмотреть можно в профиле) обязательна.