Личный сайт Андрея Сабинина

Сайт не обновляется с начала 2011 года, так как у меня на это нет ни времени, ни желания.

Заглавная / Архив блога о разработке / Разработка / Мой нативный шаблонизатор STemp
 
 

Не завидуй тому, кто силен и богат.
За рассветом всегда наступает закат.
С этой жизнью короткою, равною вздоху,
Обращайся как с данной тебе напрокат.

Омар Хайям.

 

Мой нативный шаблонизатор STemp

sap, 8-го декабря 2008 года в 18:51

Обращаю ваше внимание на то, что эта запись является архивной. Ей уже больше 4 лет. Это означает, во-первых, то, что мое мнение по поднятым в ней вопросам могло измениться (хотя, конечно, не обязательно), а во-вторых, то, что я не испытываю никакого желания эти вопросы обсуждать, и поэтому комментарии и оценки я отключил.


Шаблонизатор обновлен до версии 2.0: http://s-a-p.in/stemp/. Данная запись является архивной, советую ознакомиться с обновленной версией.

Я довольно давно уже использую нативные шаблоны, но, почему-то, у многих людей нативные шаблоны ассоциируются с конструкциями типа:

$title = 'My title';
include('templates/index.html');

<html><head><title><?php echo $title ?></title></head>
<!-- ... -->

То есть, переменную определили и приинклюдили html-файл. Я считаю, что это в корне неверный подход. Почему?

Во-первых, все переменные, переданные в шаблон, должны храниться в одном месте (свойстве класса шаблонизатора).
Во-вторых, в шаблонизаторе не должно быть доступа к переменным, которые в него не переданы, и к функциям, которые в нем не определены.
В-третьих, должен быть определен набор функций, необходимых для работы.

Таким образом, я пришел к выводу, что шаблонизатор нужен, но он не должен быть навороченным тормозом типа Smarty.
Идеология блочных шаблонизаторов (XTemplate, например) мне не импонирует потому, что в них нет ветвлений как таковых, есть только циклы.

Потому я написал свой.

Начнем с того, что нам нужно разобраться с обработкой ошибок. Я использую для этой цели исключения, потому определяем класс исключений:

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 '&nbsp;'.
     * @return string formed words
     * @access private.
     */
    
private function morph($value$word0$word1$word2$separator '&nbsp;'
    { 
        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 ?>

Скачать шаблонизатор

 −1
+8 

epsyl, 8-го декабря 2008 года в 22:08

хуле почему так мало документации и примеров?
дайошь отдельную страницу вроде http://pyha.ru/go/godb/ !

ps. что значит неверный адрес сайта? а самому http подставить? :)

sap, 8-го декабря 2008 года в 23:21

почему так мало документации и примеров?

Можно на Пыхе сделать? У меня тут немного не тот масштаб :)

ps. что значит неверный адрес сайта? а самому http подставить? :)

Обойдешься :)

Евгений, 9-го декабря 2008 года в 10:47

Всегда задавался вопросом зачем дизайнеру (верстальщику) Smarty? Зачем учить какой-то псевдо язык, когда это гораздо менее запутанно делается в лоб, на PHP. Всё-равно у верстальщика диллема — или язык смарти, или чуть-чуть PHP.

Хотя выбор очевиден — лучше понимать программиста на его языке, а не общаться хуе-как. Кстати, так повышается взаимозаменяемость между В и П. Один спокойно может помочь другому.

CTAPbIu_MABP, 9-го декабря 2008 года в 13:29

я не понимаю твоего творческого желания написать все с нуля. нет не потому что там это много времени займет, если бы эта штука была лучше смарти или квики или хотябы хтемплейта то можно было бы и времени убить а так у нее самнительный кпд. прости(((

sap, 9-го декабря 2008 года в 13:32

Ты не понимаешь, мне не нужен Смарти или Квики, мне нужен именно натив. Мне это удобней и Смарти, и Квики. Времени же на написание кода я потратил меньше, чем на написание этой статьи :)