Кто-нибудь писал шаблонизатор?
Так вот мне интересно кто как решал проблему с тем, что перемнные типа {USER_NAME}, используемые в шаблонах, могут присутствовать в контенте парсируемой информации и будут заменены шаблонизатором на значение данной переменой, хотя этого бы как раз и не хотелось :)
index.php :
// Тут пропущено подключение к бд..
$templ=implode("",file("tmplt/main.php"));
// Тут пропущены запросы в бд
$templ=str_replace("@content@", $content, $templ);
$templ=str_replace("@login_form@", $login_form, $templ);
$templ=str_replace("@site@", $site, $templ);
$templ=str_replace("@profile_info@", $profile_info, $templ);
print $templ;
mysql_close();
flush();
?>
tmplt/main.php:
с тегами например
@content@
@login_form@
@site@
и т.д.
Работает быстро.
Кстати, мне кажется, что данная конструкция:
$templ=implode("",file("tmplt/main.php"));
будет работать гораздо медленнее, чем
file_get_contents("tmplt/main.php");
или
$filename = "tmplt/main.php";
$handle = fopen($filename, "r");
$contents = fread($handle, filesize($filename));
А теперь представим, что в перменной $content имеется запись @login_form@ - получится, что она запарсится дважды.
Ты сначала попробуй. Я только что пробывал, все отлично, ничего дважды не парсится.
А теперь представим, что в перменной $content имеется запись @login_form@ - получится, что она запарсится дважды. вот это я и хочу обойти. Это может создать проблемы, если использвать данный шаблон для вывода данных, представленных пользователем. Ведь он может ввести любую переменную в текст и тем самым нарушить логику работы скрипта и даже осуществить взлом.
Кстати, мне кажется, что данная конструкция:
$templ=implode("",file("tmplt/main.php"));
будет работать гораздо медленнее, чем
file_get_contents("tmplt/main.php");
или
$filename = "tmplt/main.php";
$handle = fopen($filename, "r");
$contents = fread($handle, filesize($filename));
Тебе нужен ограничитель. Например
<login_form>
А данные которые присыалет пользователь обрабатывать через htmlspeshial.... как-то так. кароче функция которая заменят <> на ХТМЛ мнемоники.
А вообще, для пользователя угадать твой
@login_form@ равносильно угадать пароль счета в банке США=)))))))
Дыры всегда были есть и будут, но о них по сути должен знать только разработчик.
ТОЛЬКО ЭТО ВСЁ К ООП пока что и близко отношения не имеет)
Тобрабатывать через htmlspeshial.... как-то так. кароче функция которая заменят <> на ХТМЛ мнемоники.
htmlspecialchars();
Вообще то такие вещи надо знать по памяти. Тогда появится надежда что дыр все таки не будет. А миф о том что надежных приложений не бывает - не более чем миф.
Веб-мастер, вот пример твоего когда, когда он парсит дважды:
$templ="@content@
@login_form@";
$content='content@login_form@';
$login_form='login_form';
$templ=str_replace("@content@", $content, $templ);
$templ=str_replace("@login_form@", $login_form, $templ);
print $templ;
Появилась идея сделать что-то типа этого:
$npl.=str_replace("@content@", $content, $templ);
$npl.=str_replace("@login_form@", $login_form, $templ);
print $npl;
Но, почему то, выводится в браузер, так, что кажется что это все было обработано циклом.
// Определяем какие переменные используем в шаблоне
$vars[]='content';
$vars[]='login_form';
// Загружаем содержимое шаблона
$templ="@content@
@login_form@";
// Заменяем переменные шаблона с @var@ на $var
foreach($vars as $k=>$v){
$templ=str_replace('@'.$v.'@', '$'.$v, $templ);
}
// Определяем содержимое переменных шаблона
$content='content';
$login_form='login_form';
// Делаем преобразование
eval("\$output = \"$templ\";");
// Выводим результат
echo $output;
?>
Вот - придумал такое решение. Один только косяк - если в тексте шаблона попадется что-то вроде $content - то оно будет заменено на его содержимое. Тоже не очень приятный момент.
Вам надо просто что бы один шаблон не заменялся 2 раза?
З.Ы: Юзайте RegEx. Хотя работает медленнее
Как эту задачу решить?
$template='{TITLE}
{BODY}';
$tags["{TITLE}"]="Заголовок {BODY}{TITLE}";
$tags["{BODY}"]="Тело странички{BODY}{TITLE}";
echo strtr($template, $tags);
$templ = preg_replace("\@content\@", $content, $templ, 1); и заменять в обратном порядке (снизу вверх)...
Тогда можно будет использовать переменные также каскадом, но в обратном порядке. Предыдущее решение более элегантно имхо и работает скорее всего быстрее.
Вот еще вариант, только правилен ли он с т.з. безопасности скрипта?
$template='{TITLE}
{BODY}';
$tags["{TITLE}"]="Заголовок {BODY}{TITLE}";
$tags["{BODY}"]="Тело странички{BODY}{TITLE}";
echo strtr($template, $tags);
Хороший и верный вариант, респект тебе ! :)
$tag = '';
$chr = '';
$src=preg_split('//','{TITLE}
{BODY}');
$state = 0;
$def = 'Undefined';
$tags = array(
'TITLE' => "Заголовок {BODY}{TITLE}",
'BODY' => "Тело странички{BODY}{TITLE}"
);
for (;($chr = array_shift($src)) !== null;) {
switch ($chr) {
case '{':
$state = 1;
break;
case '}':
if ($state == 1) {
$target .= isset($tags[$tag]) ? $tags[$tag] : $def;
$state = 0;
$tag = '';
}
break;
default:
if ($state == 1) $tag .= $chr;
elseif ($state == 0) $target .= $chr;
break;
}
}
echo $target;
А если так, то re по-моему работает быстрее (ну или они равносильны c этим вариантом). Мне больше нравятся варианты как выше, imho, ближе к телу, больше контроля над ситуацией. Можно довести до ума, чтоб реагировать не на односимвольный экранизатор тэгов, а к примеру на те же .
если я не ошибаюсь, strstr не поможет тебе проделать то же самое, если параметры даёт класс способом типа '$this->param($name)', и не выставит дефолтные значения при отсутствии необходимого параметра в шаблонизаторе, не ругнется на синтаксис и т.п. и т.д.;) Я же не предлагаю забивать гвозди микроскопом, если задача настолько проста, можно и вовсе ограничиться таким примером (копирайт есть, но кто хозяин кода непомню)
class Template {
var $vars; /// Holds all the template variables
/**
* Constructor
*
* @param $file string the file name you want to load
*/
function Template($file = null) {
$this->file = $file;
}
/**
* Set a template variable.
*/
function set($name, $value) {
$this->vars[$name] = is_object($value) ? $value->fetch() : $value;
}
/**
* Open, parse, and return the template file.
*
* @param $file string the template file name
*/
function fetch($file = null) {
if(!$file) $file = $this->file;
extract($this->vars); // Extract the vars to local namespace
ob_start(); // Start output buffering
include($file); // Include the file
$contents = ob_get_contents(); // Get the contents of the buffer
ob_end_clean(); // End buffering and discard
return $contents; // Return the contents
}
}
?>
И писать в шаблоных обычный php, что будет работать быстрее практически любого другого шаблонизатора;)
added:
Кажется промахнулся я со своими заключениями, во всяком случае по поводу врезания, писал о strstr, а думал в этот момент о str_replace. Может кто поделится инфой, если уже таковая у кого имеется, по поводу работы .= и strstr?
added:
и кстати можно было бы как вариант вынести это в функцию (мой вариант), и принимать ссылку на контент, и по значению какого-нибудь флага решать, создавать массив символов для прогона на основе контента не трогая его (контент) или же убивать контент. Так можно сэкономить RAM, т.к. strstr не меняет "жертву". Ну, это тоже в теории=)
htmlspecialchars();
Вообще то такие вещи надо знать по памяти. Тогда появится надежда что дыр все таки не будет. А миф о том что надежных приложений не бывает - не более чем миф.
Если программируешь ежедневно на пхп.
И если работа связана с пхп.;) Когда-то и я помнил все функции.
А я знаю его основы. Я писал на нем достаточно крупные проекты.
Я рад что я знаю его основы, я не хачу на нем писать крупные проекты.
Ребят а вам не кажется что хранит всю страничку в переменной обрабатывать всю страничку на каждый код, и прочее что это является ОГРОМНОЙ УЧТЕСКОЙ ПАМЯТИ и РЕСУРСОВ? Стоит задуматься что изначально это неверный способ.
Скажу свой алгаритм шалонизатора.
Функцции герерирующие разные части текста(будь то имаги, блоки, заголовок), в зависимости от странички и передаваемых в функции параметров генерировать соостветсвующие коды. А уж в самих функциях сделать чтение из внещних ресурсов.
Грубо говоря
function_title($url);
function_center($url)
function_footer($url);
?>
То есть я говорю что вместо этого
$templ="@content@
@login_form@";
Сделать вот это
$templ=function_content()."
".function_login_form();
2ReDrum: ну так жизнь в движении;) написано-то много приблуд, но и качества они приблудного, а все хорошее - дорого, или вовсе не существует;)
Я например делал и делаю так
<?php
$pageid=PageID;
require("Header.php"); ?>
Здесь весь изменяемый текст страницы
Это любая страница которую смотрит юзер
В Header.php написано например так:
<?php
function ShutDown {
include("Footer.php");
}
register_shutdown_function("ShutDown");
// потом вывод Header'ов, подключение библиотек и т.д
<!DOCTYPE ....>
<html>
<head>
<title> <?=GetPageTitle();?> </title>
</head>
<body>
<table>
<tr>
<td>
....
</td>
</tr>
<tr>
<td>
В Footer.php конец страницы, например, так:
</td>
</tr>
</table>
<?php
// завершающие операции, например, закрытие файлов
?>
Заголовок страницы и т.д выясняются по $pageid или по $HTTP_SERVER_VARS['REQUEST_URI'] а потом задается $pageid
Вот так примерно
Я провел тут небольшой тест скорости работы наших вариантов парсера шаблонов. Результаты такие:
$n - количество левых символов в шаблоне, т.е. фактически размер файла шаблона
В процентах показывается время выполнения скрипта, т.е. нагрузка на сервер.
при $n=10
Shiizoo: 91%
MasterSID: 9%
при $n=100
Shiizoo: 95%
MasterSID: 5%
при $n=1000
Shiizoo: 99%
MasterSID: 1%
при $n=10000
Shiizoo: 100%
MasterSID: 0%
Самое интересное, что при $n==10000 твой вариант выполнялся 4.96485686302 секунд, а мой 0.00316095352173
Стандартные функции php работают намного быстрее собственных
Кстати, мне кажется for (;($chr = array_shift($src)) !== null;) можно заменить на while(($chr = array_shift($src)) !== null)
код скрипта-тестера:
// Функция для расчета Microtime (взята из мануала)
function microtime_float()
{
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
// Английский алфавит для генерации в шаблон случайного набора символов
$a='abcdefghijklmnopqrstuvwxyz';
// Базовый текст шаблона
$template='{TITLE}
{BODY}';
// Задает сколько левых символов нужно добавить в шаблон
$n=10;
// Генерируем левые символы на основе английского алфавита.
for($i=0;$i<$n;$i++){
$template.=$a[rand(0, (strlen($a)-1))];
}
// Задаем значения переменным шаблона
$title='Заголовок {TITLE}{BODY}';
$body="Тело странички{TITLE}{BODY}";
$time_start = microtime_float();
//////////////////////////////////////////////////////////////////////////////
// Начало варианта Shiizoo
//////////////////////////////////////////////////////////////////////////////
$target = '';
$tag = '';
$chr = '';
$src=preg_split('//',$template);
$state = 0;
$def = 'Undefined';
$tags = array(
'TITLE' => $title,
'BODY' => $body
);
for (;($chr = array_shift($src)) !== null;) {
switch ($chr) {
case '{':
$state = 1;
break;
case '}':
if ($state == 1) {
$target .= isset($tags[$tag]) ? $tags[$tag] : $def;
$state = 0;
$tag = '';
}
break;
default:
if ($state == 1) $tag .= $chr;
elseif ($state == 0) $target .= $chr;
break;
}
}
echo $target;
//////////////////////////////////////////////////////////////////////////////
// Конец варианта Shiizoo
//////////////////////////////////////////////////////////////////////////////
$time_end = microtime_float();
// Время выполнения варианта Shiizoo
$time_v1=$time_end-$time_start;
echo '<hr>';
$time_start = microtime_float();
//////////////////////////////////////////////////////////////////////////////
// Начало варианта MasterSID'a
//////////////////////////////////////////////////////////////////////////////
$tags["{TITLE}"]=$title;
$tags["{BODY}"]=$body;
echo strtr($template, $tags);
//////////////////////////////////////////////////////////////////////////////
// Конец варианта MasterSID'a
//////////////////////////////////////////////////////////////////////////////
$time_end = microtime_float();
// Время выполнения варианта MasterSID
$time_v2=$time_end-$time_start;
// Общее время работы двух вариантов
$total=$time_v1+$time_v2;
// Вывод расчетов
echo '<hr>Shiizoo: '.round($time_v1*100/$total).'%
MasterSID: '.round($time_v2*100/$total).'%';
?>
MasterSID: 9%
при $n=100
Shiizoo: 95%
MasterSID: 5%
при $n=1000
Shiizoo: 99%
MasterSID: 1%
при $n=10000
Shiizoo: 100%
MasterSID: 0%
Это отчасти результат разбиения строки на символы preg_split'ом. Если заменить на прямое обращение к символу строки str{n}, будет примерно 20 на 80. Если умело заоптимизировать код то возможно он будет выдавать приемлимые результаты. А смысл как я уже писал есть— полноценный контроль над обработкой шаблона без необходимости делать callback'и т.п., что опять же сожрало бы лишние ресурсы.
;) Естественно компиленный код будет рботать быстрее.
Это отчасти результат разбиения строки на символы preg_split'ом. Если заменить на прямое обращение к символу строки str{n}, будет примерно 20 на 80. Если умело заоптимизировать код то возможно он будет выдавать приемлимые результаты. А смысл как я уже писал есть— полноценный контроль над обработкой шаблона без необходимости делать callback'и т.п., что опять же сожрало бы лишние ресурсы.
added:
Ой нагнал, 5 и 95 =)
Я нашел быстрое решение!
Вот код:
$template = file_get_contents ($filename);
foreach ($replaces as $search => $replace) {
$replace = str_replace ("%", "%\\\\", $replace);
$template = str_replace ("%" . $search . "%", $replace, $template);
}
$template = str_replace ("%\\\\", "%", $template);
return ($template);
}