ФорумПрограммированиеПыхнуть хотите?Готовые решения → STemp — мой шаблонизатор

STemp — мой шаблонизатор

  • sap

    Сообщения: 2701 Репутация: N Группа: Кто попало

    Spritz 22 ноября 2008 г. 16:35

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

    $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 } ?>


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

    Использование класса в личных нуждах разрешено без ограничений. При перепечатке статьи или исходного кода, в том числе, частично, ссылка на меня (на мой сайт, посмотреть можно в профиле) обязательна.
  • Lirck

    Сообщения: 1658 Репутация: N Группа: Джедаи

    Spritz 22 ноября 2008 г. 17:56, спустя 1 час 20 минут 18 секунд

    а зачем нужно это?

    private $template;


    Свойство ведь только в методе display используется.
  • sap

    Сообщения: 2701 Репутация: N Группа: Кто попало

    Spritz 22 ноября 2008 г. 17:56, спустя 40 секунд

    Ну вот для метода display и нужно :)
  • Lirck

    Сообщения: 1658 Репутация: N Группа: Джедаи

    Spritz 22 ноября 2008 г. 18:01, спустя 4 минуты 31 секунду

    Тогда зачем свойство вообще? Можно просто в методе display переменную объявить.
  • sap

    Сообщения: 2701 Репутация: N Группа: Кто попало

    Spritz 22 ноября 2008 г. 18:03, спустя 2 минуты 19 секунд

    Ну я ебу, может кто-нибуть захочет дополнить, чтобы можно было не только выводить, а и в файл сохранять, например. Пусть будет свойством.
  • Lirck

    Сообщения: 1658 Репутация: N Группа: Джедаи

    Spritz 22 ноября 2008 г. 18:06, спустя 2 минуты 41 секунду

    Если уж используешь метод __get, то вместо assign можно использовать __set :)
  • md5

    Сообщения: 11960 Репутация: N Группа: в ухо

    Spritz 22 ноября 2008 г. 18:09, спустя 3 минуты 2 секунды

    как-то не очень будет делать
    $view->__set('var', $value);

    все умрут, а я изумруд
  • Trej Gun

    Сообщения: 5305 Репутация: N Группа: в ухо

    Spritz 22 ноября 2008 г. 18:11, спустя 2 минуты 33 секунды

    sap, ща тя айви научит

    md5, нормально

    $view->var = $value;
  • sap

    Сообщения: 2701 Репутация: N Группа: Кто попало

    Spritz 22 ноября 2008 г. 18:12, спустя 50 секунд

    md5, если юзать __set, тогда уже будет $view->var = value;
    Мне кажется, это плохо выглядит. Потому что выглядит, как назначение свойства, а нужна передача переменной. Именно потому assign. Чтобы подчеркнуть, какое именно это действие.
  • Lirck

    Сообщения: 1658 Репутация: N Группа: Джедаи

    Spritz 22 ноября 2008 г. 18:13, спустя 28 секунд

    $view->__set('var', $value);


    $view->var = $value
  • Lirck

    Сообщения: 1658 Репутация: N Группа: Джедаи

    Spritz 22 ноября 2008 г. 18:19, спустя 6 минут 7 секунд

    Мне кажется, это плохо выглядит. Потому что выглядит, как назначение свойства, а нужна передача переменной. Именно потому assign. Чтобы подчеркнуть, какое именно это действие.

    Тогда вместо __get лучше сделать get.
  • sap

    Сообщения: 2701 Репутация: N Группа: Кто попало

    Spritz 22 ноября 2008 г. 18:23, спустя 3 минуты 43 секунды

    Чтобы в шаблоне писать <?php echo $this->get("variable") ?>? Да ну нахуй.
  • Lirck

    Сообщения: 1658 Репутация: N Группа: Джедаи

    Spritz 22 ноября 2008 г. 18:24, спустя 59 секунд

    Зато не перепутаешь это со свойством (:
  • sap

    Сообщения: 2701 Репутация: N Группа: Кто попало

    Spritz 22 ноября 2008 г. 18:24, спустя 40 секунд

    Будет большая херня с циклами, foreach ($this->get("array") as $key=>$value) я так подозреваю вообще ошибку выдаст.
  • Trej Gun

    Сообщения: 5305 Репутация: N Группа: в ухо

    Spritz 22 ноября 2008 г. 18:47, спустя 22 минуты 30 секунд

    sap, невыдаст

    незнаю только сколько раз вызовиться гетер но ошибки точно не будет

Пожалуйста, авторизуйтесь, чтобы написать комментарий!