webew
Войти » Регистрация
 
PHP
HTML

Легкий шаблонизатор на PHP

13 мая 2011, 21:46
Автор: Михаил Серов [1234ru]

Вниманию читателей предлагается простой в использовании обработчик шаблонов на языке PHP с поддержкой условных конструкций, циклов, подключения вложенных шаблонов и запуска сторонних модулей (функций). Время обработки шаблона составляет в среднем 20-70 мс. Для работы требуется подключение одного php-файла размером 20 Кб (600 строк кода). Код стабилен и применяется на нескольких работающих проектах.

Перейдём сразу к практике и посмотрим, как выглядит шаблон, взяв для примера немного упрощенную версию HTML-кода сайта webew.ru:

<html>
<head>
    /* Многострочные комментарии - как в C или PHP */
   
    <title>{*title*}</title>
   
    <base href="{*=BASEHREF*}"> /* константа */
   
    /* условные конструкции: */
   
    {?*keywords*}
        <meta name="keywords" content="{*keywords*}">
    {*keywords*?}
   
    {?*description*}
        <meta name="description" content="{*description*}">
    {*description*?}
</head>
<body>
    <div id="logo">
        <a href=""><img src="{*logo.image*}" alt="{*logo.alt*}"></a>
    </div>

    <div id="menu">
       
        /* цикл: */
       
        {%*menu*}
            <a href="{*menu:url*}">{*menu:title*}</a>
        {*menu*%}
   
    </div>
   
    <div id="content">
        {*content*}
    </div>
   
    <div id="footer">
        webew.ru &copy; {*year*}
    </div>
</body>
</html>
 

А вот соответствующий код PHP:


ini_set('pcre.backtrack_limit', 1024*1024); // (см. ниже)

$DATA['title'] = 'Webew: теория и практика веб-технологий ';

define('BASEHREF', 'http://webew.ru/');

$DATA['logo'] = array(
    'image' => 'i/logo.gif',
    'alt' => 'Логотип webew.ru'
    );

$DATA['menu'] = array(
    array('url' => 'css', 'title' => 'CSS'),
    array('url' => 'php', 'title' => 'PHP'),
    array('url' => 'seo', 'title' => 'Интернет-маркетинг'),
    array('url' => 'c', 'title' => 'C/C++')
    );

$DATA['content'] = 'Приветствуем вас на webew.ru!';


require_once 'websun.php'; // подключаем файл с шаблонизатором

$tpl = 'templates/main.tpl'; // путь к шаблону

$html = websun_parse_template_path($DATA, $tpl); // запуск шаблонизатора

echo $html; // получили обработанный шаблон, отдаем клиенту результат
 

Несмотря на то, что переменные $DATA['keywords'] и $DATA['description'] не установлены, при обработке шаблона не возникнет никаких ошибок или предупреждений.

Условная конструкция вида {?*keywords*} что-то {*keywords*?} означает "вставить что-то, если в переменной, переданной шаблону (в данном случае $DATA) присутствует элемент keywords и при этом он не является пустой строкой, нулём, FALSE, NULL или пустым массивом". Соответственно, конструкция {?!*keywords*} ... {*keywords*?!} срабатывает, если какое-нибудь из этих условий не выполняется.

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

В цикле ключи массива указываются через двоеточие — {*menu:title*}, а вне цикла через точку — {*logo.image*}.

Можно также обращаться к переменным глобальной области видимости, в т.ч. суперглобальным массивам. Для этого перед именем переменной нужно поставить знак доллара. Например, подстановка в шаблон переменной $_GET['foo'] выглядит так: {*$_GET.foo*}.

Вложенные шаблоны

Обычно код бывает удобно разбить на несколько шаблонов, которые затем подключать по мере необходимости. Пусть шаблон меню находится в файле menu.tpl. Тогда код из главного шаблона

<div id="menu">
    {%*menu*}
        <a href="{*menu:url*}">{*menu:title*}</a>
    {*menu*%}
</div>

в неизменном виде переместится в menu.tpl, а вместо него будет

...
<div id="logo">
    ...
</div>

{* +menu.tpl *} /* подключаем шаблон меню */

<div id="content">
...

Путь к шаблону может также передаваться в переменной. Например, если записать путь к шаблону меню в $DATA['menu_template'], то подключение его примет вид

{* + *menu_template* }

Существует несколько способов указания путей к шаблонам.

Самый наглядный метод — это указание абсолютного пути к файлу шаблона в файловой системе — что-то наподобие

$tpl = '/home/webew.ru/htdocs/templates/main.tpl'

Аналогично, для подключения шаблона меню с помощью абсолютного пути приходится писать

{* +/home/webew.ru/htdocs/templates/menu.tpl *}

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

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

websun_parse_template_path(
    $DATA,
    $tpl,
    '/home/webew.ru/htdocs/templates'
    );


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

Сообщать шаблонизатору о том, что интерпретировать путь нужно относительно корневого каталога шаблонов, следует, поместив в начало пути символ ^:

$tpl = '^/main.tpl';

Можно указывать также путь к шаблону относительно корневого каталога веб-сервера (используется переменная $_SERVER[DOCUMENT_ROOT]). Для этого в начало пути нужно поместить знак доллара.

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

// пусть корневой каталог веб-сервера - /home/webew.ru/htdocs
// корневой каталог шаблонизатора - /home/webew.ru/htdocs/templates
// тогда следующие четыре записи эквивалентны:

$tpl = 'menu.tpl'; // путь относительно текущего шаблона
$tpl = '^/menu.tpl'; // относительно корневого каталога шаблонизатора
$tpl = '$/templates/menu.tpl'; // относительно корневого каталога веб-сервера
$tpl = '/home/webew.ru/htdocs/templates/main.tpl'; // абсолютный путь для ФС

При запуске шаблонизатора (вызов функции websun_parse_template_path) и при подключении не связанных друг с другом шаблонов рекомендуется использовать путь относительно корневого каталога шаблонизатора, в остальных случаях как правило удобнее пользоваться относительными путями.


Передача во вложенный шаблон части массива данных

Зачастую подключаемый шаблон имеет дело не со всем массивом данных, а только с какой-то его частью (например, в шаблоне меню не нужны никакие переменные, кроме $DATA[menu]). Записывать каждый раз префикс массива в таком шаблоне становится излишним и неудобным, код шаблона загромождается. Чтобы этого избежать, подключаемому шаблону можно передать не весь массив, а лишь нужный элемент.

Вот как будут выглядеть основной шаблон и шаблон меню при таком подходе:

(основной шаблон)

...

{* + *menu* | menu.tpl *}

/* путь к шаблону в таком случае указываем через вертикальную черту */

...

(или другой вариант — с передачей пути в переменной:)

{* + *menu* | *menu_template* *}

Шаблон меню:

<div id="menu">
    {%**}
        <a href="{*:url*}">{*:title*}</a>
    {**%}
</div>

Конструкции вида {**}, {%**} и {?**} означают обращение к корневой переменной, переданной в шаблон. А запись вида {* + шаблон *} есть укороченная форма конструкции {* + ** | шаблон *} (** — в подключаемый шаблон передается вся корневая переменная).

Можно пойти еще дальше и вынести код для элемента меню в отдельный шаблон:

<div id="menu">
    {%**} {* + *:* | menu-item.tpl *} {**%} 
</div>

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

menu-item.tpl:

<a href="{*url*}">{*title*}</a>

Вызов сторонних модулей

Есть возможность вызывать специально подготовленные функции-модули прямо из шаблона. Например, вместо $DATA['year'] = date('Y') и

{*year*}

можно записать

{* @year *}

а в PHP:

function module_year() { return date('Y'); }

Как видно из примера, имя вызываемой таким образом функции должно содержать префикс module_ (подобное ограничение пространства имен введено из соображений безопасности — чтобы из шаблона нельзя было вызвать какую угодно функцию, а набор их явно и сознательно контролировался разработчиками). Если функции с таким названием нет, будет сгенерирована ошибка уровня E_WARNING.

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

{* @menu | menu.tpl *}

Имя функции можно также передавать шаблону в переменной. Например, если $DATA['menu_module'] = 'menu' и $DATA['menu_template'] = 'menu.tpl', то вызов будет выглядеть так:

{* @*menu_module* | *menu_template* *}

Если префикс module_ уже присутствует в переменной, шаблонизатор не будет повторно его подставлять (т.е. можно записать как $DATA['menu_module'] = 'menu', так и $DATA['menu_module'] = 'module_menu').

Вызываемым таким образом функциям можно передавать аргументы, которыми могут выступать как скалярные величины, так и переменные шаблона. В примере ниже функции module_menu передаются три аргумента — переменные $_SERVER[REQUEST_URI] и $DATA[title], а также строка '1':

{* @menu(*$_SERVER.REQUEST_URI*,*title*,1) | menu.tpl *}

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

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

Скачать

Чтобы начать использовать шаблонизатор, достаточно скачать и подключить его php-файл.

Еще кое-что

Ниже приводится некоторая дополнительная информация, которая может быть полезной при работе с шаблонизатором.


Помимо websun_parse_template_path, принимающей путь к шаблону, есть еще функция websun_parse_template, которой вместо пути шаблона передается его содержимое:

$DATA = ...
$template = '
    {*keywords*}
    ...
    '
;
$html = parse_websun_template(
    $DATA,
    $template,
    '/home/webew.ru/htdocs/templates' // можно не указывать
    );

(на самом деле обработчик шаблонов реализован в виде объекта, создание и настройка которого для удобства инкапсулированы в функции websun_parse_template и websun_parse_template_path; если интересно, как это происходит, можно посмотреть код этих функций)


Условия можно конструировать с использованием сравнения:

{?*a=1*} a равно 1 {*a=1*?}

{?!*a=1*} a не равно 1 {*a=1*?!}

{?*a>1*} a больше 1 {*a>1*?}

{?!*a>1*} a не больше (т.е. меньше или равно) 1 {*a>1*?!}

{?*a=b*} а равно b {*a=b*?}

{?*a="раз"*} a - это "раз" {*a="раз"*?}

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

Для ясности следует отметить, что условные конструкции можно использовать и внутри циклов. Например, можно выделить активный пункт меню, поместив его в <strong> вместо обычных ссылок:

$DATA['menu'] = array(
    array('url' => 'css', 'title' => 'CSS'),
    array('url' => 'php', 'title' => 'PHP', 'active' => TRUE), // активный пункт меню
    array('url' => 'seo', 'title' => 'Интернет-маркетинг'),
    array('url' => 'c', 'title' => 'C/C++')
    );

Шаблон:

{%*menu*}
    {?!*menu:active*}<a href="{*menu:url*}">{*menu:title*}</a>{*menu:active*?!}
    {?*menu:active*}<strong>{*menu:title*}</strong>{*menu:active*?}
{*menu*%}

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


Иногда сократить запись можно с помощью конструкции вида {*var1|var2|var3|"раз"*}, которая заменяется на первую из перечисленных непустую альтернативу (в данном примере, если все переменные окажутся пустыми, будет подставлена строка "раз").


При использовании PHP 5.2.2 и выше следует обратить внимание на настройку pcre.backtrack_limit, которая ограничивает максимальный размер строки, обрабатываемой функциями семейства preg (по умолчанию это 100 Кб). Характерный признак недостаточно большой величины pcre.backtrack_limit — получение пустой строки в результате обработки шаблона в случае, когда этого быть не должно. Избежать этого можно, увеличив это значение (лучше с большим запасом — к растрате ресурсов это не приведет):

ini_set('pcre.backtrack_limit', 1024*1024); // 1 Mб



Итерация по одномерному массиву (вида $DATA['list'] = array(10,20,30,40,50)) делается очень просто. Например, вывести его элементы по одному в строке можно так:

{%*list*} {*list:*}<br> {*list*%}

А вообще можно использовать массивы любой размерности (не только двумерные), а циклы строить — любой вложенности:

$DATA['cities'] = array(
    array(
        'id' => 1,
        'title' => 'Москва',
        'codes' => array(77, 97, 99, 177, 197),
        'neighbours' =>
            array('id' => 25, 'title' => 'Калуга'),
            array('id' => 32, 'title' => 'Тула'),
            array('id' => 47, 'title' => 'Владимир'),
            array('id' => 59, 'title' => 'Рязань'),
            array('id' => 134, 'title' => 'Тверь'),
            array('id' => 15, 'title' => 'Смоленск'),
            array('id' => 37, 'title' => 'Ярославль')
            ),
        ),
    array(
        'id' => 2,
        'title' => 'Санкт-Петербург',
        'codes' => array(78, 98),
        'neighbours' =>
            array('id' => 69, 'title' => 'Петрозаводск'),
            array('id' => 78, 'title' => 'Вологда'),
            array('id' => 10, 'title' => 'Новгород'),
            array('id' => 117, 'title' => 'Псков')
        ),
    ...
    );

Шаблон:

{%*cities*}
    <h1>{*cities:title*}</h1>
   
    <p>
    Автомобильные коды:
    {%*cities:codes*}
        {*cities:codes:*} /* список кодов - одномерный массив */
    {*cities:codes*%}
    </p>
   
    <p>
    Соседние регионы:
    <br>
    {%*cities:neighbours*}
        <a href="/city/{*cities:neighbours:id*}">
        {*cities:neighbours:title*}
        </a>
        <br>
    {*cities:neighbours*%}
    </p>
{*cities*%}

К элементам такого массива можно обращаться и вне цикла, например, {*cities.0.neighbours.1.title*} — 'Тула' и т.п.

Внутри цикла есть также возможность обратиться к ключу массива - с помощью инструкции ^KEY, например:

{%*cities*}
    № {*cities:^KEY*}. {*cities:title*}<br>
{*cities*%}

А с помощью ^COUNT можно оперировать числом элементов в массиве:

Всего городов: {*cities^COUNT*}.

Подключение шаблонов внутри циклов можно переписать покороче:

/* можно так: */
{%*menu*} {* +*menu:* | menu-item.tpl *} {*menu*%}

/* но лучше - так (перед именем передаваемой в шаблон переменной появился знак %) */
{* +*%menu* | menu-item.tpl *}

Аналогичное сокращение действует и для подключения шаблона внутри условия:

/* можно так: */
{?*menu*} {* +*menu* | menu-item.tpl *} {*menu*?}

/* но лучше - так (перед именем переменной появился знак вопроса) */
{* +*?menu* | menu-item.tpl *}

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

    {* + >*tpl* *}
        /* фактически дополняем переменной  
        текущий шаблон; так можно, грубо говоря,
        сделать её содержимое динамическим
        с помощью шаблонизатора */
       
    {* + *subvar* | >*tpl* *}
        /* можно и часть массива */
       
    {* + *?subvar* | >*tpl* *}
        /* если в subvar что-то есть.. */
       
    {* + *%subvar* | >*tpl* *}
        /* пройтись по переменной в цикле */
   
    {* + *@module* | >*tpl* *}
        /* дать переменную модулю как шаблон */

© Все права на данную статью принадлежат порталу webew.ru. Перепечатка в интернет-изданиях разрешается только с указанием автора и прямой ссылки на оригинальную статью. Перепечатка в печатных изданиях допускается только с разрешения редакции.
Добавить комментарий
Отображение комментариев: Древовидное | Плоское
NO USERPIC

skif1965

Здравствуйте
Очень познавательная статья.
Я из начинающих и если можно разъяснить некоторые вопросы , подскажите.
Как в $DATA['content'] = 'Приветствуем вас на webew.ru!'; вместо (Приветствуем вас на webew.ru!) вставить несколько готовых PHP файлов.
У меня почему то отображается только название.
И если это возможно как вызывать их в шаблоне.
Может как то не правильно выразился но суть понятна.
14.08.2011, 15:10
Ответить

1234ru

Что подразумевается под "вставить несколько готовых PHP файлов"?
Вы имеете в виду, что $DATA['content'] должна набираться в нескольких файлах?
Если так, то нужно примерно вот как:

$DATA['content'] = '';

require_once 'файл1.php';
require_once 'файл2.php';
...


В файлах:
$DATA['content'] .= 'что-то... (то, что требуется)';


И так - в каждом из файлов.
То, что не убивает нас, делает нас инвалидами.
16.08.2011, 16:06
Ответить

Morty

$DATA['content'] = '';

require_once 'файл1.php';
require_once 'файл2.php';
...

Нифига не работает.
Запрашиваемый файл вставляется вверх страницы, независимо от того где вставен шаблонный тег.
Сделайте функцию инклуда в шаблонизаторе.
Чтоб можно было вставить файл как-то так
{*include_file="filename.php"*}
17.01.2012, 04:36
Ответить

1234ru

Цитата:
Запрашиваемый файл вставляется вверх страницы

Тут что-то не так..
Сами файлы никуда не вставляются (если Вы, конечно, не имеете в виду, что в каждом из них написали echo).

Покажите, что у Вас происходит в файлах, или хотя бы расскажите на примере, чего хотите добиться.
То, что не убивает нас, делает нас инвалидами.
18.01.2012, 01:06
Ответить

Morty

1234ru
или хотя бы расскажите на примере, чего хотите добиться.

Morty
Чтоб можно было вставить файл как-то так
{*include_file="filename.php"*}

То есть я хочу, чтоб можно было подключать php файлы непосредственно в файле шаблона (some_template.tpl).
19.01.2012, 01:29
Ответить

1234ru

Подключать PHP-файл так, чтобы в нём выполнился код, шаблонизатор не позволяет.

Вам наверняка подойдет возможность вызывать из шаблона модули.

В терминологии шаблонизатора модули - это обычные функции PHP, имя которых начинается с "module_".
Модули можно вызывать из кода шаблонов специальной конструкцией, при этом либо передавая результат их действия в какой-то шаблон, либо непосредственно заменяя им конструкцию в шаблоне.
Почитайте статью отсюда: http://webew.ru/articles/3609.webew#modules

Хотя не очень понятно, зачем Вам понадобилось прямо в шаблоне PHP-файлы вызывать.
Почему бы вместо этого просто не заполнить в этих файлах какие-либо переменные, а потом в каком-то общем шаблоне эти переменныые использовать?.
То, что не убивает нас, делает нас инвалидами.
19.01.2012, 14:28
Ответить
NO USERPIC

maber

а можно какой нибудь не большой пример с базой mysql?
25.08.2011, 22:30
Ответить

1234ru

Вообще шаблонизатор от базы никак не зависит.

Ну, можно представить, что данные страницы берутся из базы. Допустим, есть таблица pages:

CREATE TABLE pages (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255),
    keywords VARCHAR(255),
    description VARCHAR(255),
    text TEXT
)


Шаблон:

<html>
<head>
    <title>{*page.title*}</title>
    <meta name="keywords" content="{*page.keywords*}">
    <meta name="description" content="{*page.description*}">
</head>
<body>
   {* +header.tpl *}
   /* (тут некая "шапка" - логотип и т.д.) */

   <div class="content">
    {*page.text*}
   </div>
   
   {* +footer.tpl *}
</body>
</html>



Кусок кода, отвечающий за получение данных страницы:
// где-то раньше из строки адреса
// получили id страницы
$sql = "
    SELECT
        title,
        description,
        keywods,
        content
    FROM pages
    WHERE id = $pageid
    "
;
$DATA['page'] = mysql_getrow($sql);


Про mysql_getrow() см. здесь:
Удобные функции PHP для работы с MySQL.
То, что не убивает нас, делает нас инвалидами.
26.08.2011, 03:58
Ответить
NO USERPIC

maber

извиняюсь :) точнее хотел спросить про то что как все записи вывести с таблицы
26.08.2011, 14:55
Ответить

1234ru

Вообще это, опять же, к шаблонизатору не относится, но
SELECT * FROM таблица
То, что не убивает нас, делает нас инвалидами.
26.08.2011, 17:48
Ответить
NO USERPIC

maber

это понятно что так :) просто что-то у меня не получается построить шаблон :) использовал удобные функции)
27.08.2011, 14:13
Ответить

1234ru

Покажите код, который у вас данные получает.
То, что не убивает нас, делает нас инвалидами.
27.08.2011, 15:23
Ответить
NO USERPIC

WidowMaker

Спасибо, интересная статья, очень похоже на ClearSilver.
28.11.2011, 01:11
Ответить

1234ru

Спасибо за наводку.
Надо будет поглядеть на его исходный код, сравнить.
То, что не убивает нас, делает нас инвалидами.
28.11.2011, 06:59
Ответить
NO USERPIC

axules

Я в веб программировании соооовсем недавно и решил прокачаться, в общем я искал в нете шаблонизаторы и нашел вашу разработку =)
Уважуха =) хороший простой функциональный шаблонизатор!
НО! обязательно напишите что он требует PHP 5.3 (ну или не меньше 5.2.4)
Щас расскажу почему и как сделать его менее чувствительным к версии:
В коде шаблонизатора вы рвете код шаблона на куски при помощи регулярных выражений, но увы старые версии не тянут в регулярных выражениях подмены имен переменных
(с цифр на имена, вот она конструкция подмены - ?P<VAR> - это не работает)
Как исправить?
просто переписать код шаблонизатора - используемые переменные заменить на порядковый индекс в массиве разбора или в описании написать версию ПХП =)
Как я заметил? - условия воообще игнорировались, залез в исходник, вывел массив разбора - нема имен переменных =) ну это был хороший повод сменить версию пхп =)

В общем я вам благодарен за шаблонизатор!
13.12.2011, 23:45
Ответить

1234ru

axules, спасибо на добром слове :)

Что касается версий PHP. Тут меня ввела в заблуждение документация: именованные подшаблоны появились в PCRE 4.0, но в документации PHP соответствия версии PCRE я нигде не нашел.
Стоит добавить, что эту версию шаблонизатора я изначально разрабатывал на PHP 5.2+, поэтому про то, что именованные подшаблоны не поддерживаются более ранними версиями, узнал, когда всё уже было написано.
Переписывать код ради обратной совместимости с порядочно устаревшими версиями как-то лень, честно: обновиться до 5.2 сейчас обычно не проблема (чего нельзя сказать о 5.3), так что будет лишний повод идти в ногу со временем :)
То, что не убивает нас, делает нас инвалидами.
14.12.2011, 13:05
Ответить
NO USERPIC

axules

Блин а я как то не в теме различий 5.2 и 5.3=( лень сидеть читать =(
А что это настолько проблематично с 5.2 перенести систему на 5.3? Такие глобальные перемены?
(Мне то это удалось безболезненно - система в начале разработки да и ничего сверхъестественного не использую, но другие чтото не проверил ... ... ... надо бы проверить ...)
14.12.2011, 21:49
Ответить

1234ru

Трудности возникают при использовании устаревших функций (например, из семейства ereg) - код работает, но начинают сыпаться warning'и.
Если проект начал разрабатываться недавно, то проблем быть не должно.
То, что не убивает нас, делает нас инвалидами.
15.12.2011, 15:53
Ответить

1234ru

Версия 0.1.21 дополнена следующими возможностями (текст статьи обновлен соответственно):


1. Можно сравнивать между собой две переменные:
{?*a=b*}a равно b{*a=b*?}
{?*a>b*}a больше b{*a>b*?}

Чтобы сравнивать переменную со строкой, последнюю нужно заключить в двойные кавычки:
{?*a="раз"*}a - раз!{*a="раз"*?}

2. Конструкция вида {*var1|var2|var3|"раз"*} заменяется на первую из перечисленных непустую (!= FALSE) переменную (а в данном примере, если все переменные окажутся пустыми, будет подставлена строка 'раз').


3. С помощью инструкции ^COUNT можно получить в шаблоне количество элементов в массиве: вида {*var^COUNT*} (если переменная не является массивом, будет подставлена пустая строка).


4. Инструкция ^KEY позволяет получать ключ элемента массива внутри циклов:
{%*var*}
    {*var:^KEY*} - ключ
    ...
{*var*%}
То, что не убивает нас, делает нас инвалидами.
27.12.2011, 12:06
Ответить

Morty

Отличная статья, отличный шаблонизатор но есть вопрос...
К примеру мне нужно в цикл шаблонизатора вставить цикл for.
Делаю таким образом
$max =5;
$DATA['cycle'] = array(
    for ($x=0; $x <= $max; $x++) {
        array('t1' => 'T'.$x, 't2' => 'T'.$x, 't3' => 'T'.$x)
    }
);

В ответ получаю:
Parse error: syntax error, unexpected T_FOR, expecting ')' in index.php on line 21
21 строка
for ($x=0; $x <= $max; $x++) {

Помогите пожалуйста.
17.01.2012, 01:41
Ответить

1234ru

Morty, это у Вас ошибка в коде PHP, который формирует данные.
Нельзя прямо в объявлении массива писать цикл, надо так:

$DATA['cycle'] = array();
for ($x = 0; $x <= $max; $x++)
    $DATA['cycle'][] = array(
            't1' => 'T'.$x,
            't2' => 'T'.$x,
            't3' => 'T'.$x,
        );


Соответственно, в шаблоне пишете, например

$tpl = '
{%*cycle*}
    cycle[{*cycle:^KEY*}] - {*cycle:t1*}
    ---
{*cycle*%}'
;

echo websun_parse_template(
    $DATA,
    $tpl
    );


Результат:

    cycle[0] - T0
    ---

    cycle[1] - T1
    ---
   
    и т.д...
То, что не убивает нас, делает нас инвалидами.
18.01.2012, 01:10
Ответить

Morty

Спасибо
12.02.2012, 05:56
Ответить

Morty

Как сделать чтоб нормально работал вложенный цикл?
Есть два цикла. Первый выводит список категрий, второй - список подкатегорий для каждой категории (если подкатегории есть).
$all = mysql_result(mysql_query("SELECT COUNT(*) FROM `cat`"), 0, 0);
if ($all>0)
{
$DATA['cats'] = array();
$sql = mysql_query("SELECT * FROM `cat` ORDER BY `id` DESC");
while ($row = mysql_fetch_assoc($sql))
{
    $cat_id = $row['id'];
    $name = $row['name'];
    $catpost = mysql_result(mysql_query("SELECT COUNT(*) FROM `post` WHERE `cat_id`='$cat_id'"), 0, 0);
    $DATA['cats'][] = array(
        'punkt' => '<a href="'.$home.'/cat/'.$cat_id.'">'.$name.'</a> ('.$catpost.')<br>',
    );
}
$suball = mysql_result(mysql_query("SELECT COUNT(*) FROM `subcat` WHERE `pid` = '$cat_id'"), 0, 0);
    if ($suball>0){
        $DATA['subcats'] = array();
        $ssql = mysql_query("SELECT * FROM `subcat` WHERE `pid` = '$cat_id' ORDER BY `id` ASC");
        while ($srow = mysql_fetch_array($ssql))
        {
            $sid = $srow['id'];
            $sname = $srow['name'];
            $scpost = mysql_result(mysql_query("SELECT COUNT(*) FROM `post` WHERE `subcat_id` = '$sid'"), 0, 0);
            $DATA['subcats'][] = array(
                'subpunkt' => '&nbsp;&nbsp;<a href="/cat/'.$sid.'/1">'.$sname.'</a> ('.$scpost.')<br>',
            );
        }
    }
}

В шаблоне пишу так
           {%*cats*}
                {*cats:punkt*}
                {%*subcats*}
                    {*subcats:subpunk*}
                {*subcats*%}
            {*cats*%}


И оно отображает подкатегории возле каждой категории даже если подкатегории не относятся к данной категррии.
Как это исправить?
12.02.2012, 06:24
Ответить

1234ru

Morty
оно отображает подкатегории возле каждой категории даже если подкатегории не относятся к данной категррии.


Ну, Вы же массив так составили - вот оно и отображает.
Через print_r() сначала посмотрите, как ваш массив выглядит.

При нормальной структуре массива шаблон должен примерно вот так выглялдеть:

{%*cats*}
    {*cats:punkt*}
    {%*cats:subcats*} /* у каждой категории - свой подмассив с subcats */
        {*cats:subcats:subpunk*}
    {*cats:subcats*%}
{*cats*%}
То, что не убивает нас, делает нас инвалидами.
14.02.2012, 02:30
Ответить

Morty

Наверное проще будет модулем это дело подключить...
14.02.2012, 11:00
Ответить

Morty

....
17.01.2012, 01:43
Ответить
NO USERPIC

Virtus-pro

Здравствуйте, ошибка с условиями.

Не на одном хостинге уже сталкиваюсь с проблемой, что условия не работают

Вот с одно из хостеров:
Операционная система: Linux
Версия php: 5.2.6-1+lenny16
Версия GD: 2.0 or higher
Версия MySQL: 5.0.51a-24+lenny5
Размер БД MySQL: 40.2 кб.

код даже если вставить ваш:
<html>
<head>

</head>
<body>
{?*a=1*} a равно 1 {*a=1*?}

{?!*a=1*} a не равно 1 {*a=1*?!}

{?*a>1*} a больше 1 {*a>1*?}

{?!*a>1*} a не больше (т.е. меньше или равно) 1 {*a>1*?!}

{?*a=b*} а равно b {*a=b*?}

{?*a="раз"*} a - это "раз" {*a="раз"*?}


<br />
a = "{*a*}"

</body>
</html>


в php файле так
require_once 'websun.php'; // подключаем файл с шаблонизатором

$tpl = 'test.html'; // путь к шаблону
$DATA['a'] = "1";
$html = websun_parse_template_path($DATA, $tpl); // запуск шаблонизатора

echo $html; // получили обработанный шаблон, отдаем клиенту результат   


ошибки возникают типа
Notice: Undefined index: varname in /var/www/*****/data/www/*****/lib/templates/websun.php on line 242

Notice: Undefined index: value in /var/www/*****/data/www/*****/lib/templates/websun.php on line 250


Подмечу, если просто ставить условие, то ошибки нету, но и ничего не отображается при любом условии
Если в цикле делать условие, то возникает такая ошибка, содержимое цикла показывается, но условия не одни не выполняются


Хотя на других хостингах все прекрасно работает

Строки из файла с ошибками

$variable_value = $this->var_value($matches['varname']);
..........
$compare_value = ($matches['value'])


функция в целом откуда эти строки
    function parse_if($matches) {
        $variable_value = $this->var_value($matches['varname']);
       
        // применять variable_value
        // для получения значения $compare_value
        // нельзя при пустой строке,
        // т.к. variable_value устроена так,
        // что при пустой строке
        // она возвращает корневой массив
        $compare_value = ($matches['value'])
            ? $this->var_value($matches['value'])
            : FALSE ;
       
        if (isset($matches['sign']) AND $matches['sign']) {
            switch($matches['sign']) {
                case '=': $check = ($variable_value == $compare_value); break;
                case '>': $check = ($variable_value > $compare_value); break;
                case '<': $check = ($variable_value < $compare_value); break;
            }
        }
        else
            $check = ($variable_value == TRUE);
           
        $result = ($matches[1] == '?')
            ? $check
            : !$check ;
           
        $parsed_if = ($result)
            ? $this->find_and_parse_if($matches['body'])
            : '' ;
           
        return $parsed_if;
    }
15.02.2012, 20:22
Ответить

1234ru

Не работают именованные подшаблоны.
Получается так из-за того, что используется устаревшая версия библиотеки PCRE. Это странно, т.к. в PHP 5.2 и выше обычно PCRE достаточно новая (может, на том хостинге сборка такая).
Что показывает phpinfo()?
То, что не убивает нас, делает нас инвалидами.
15.02.2012, 22:00
Ответить
NO USERPIC

Virtus-pro

pcre


PCRE (Perl Compatible Regular Expressions) Support------------enabled
PCRE Library Version ---------------------------------------------------7.6 2008-01-28

Directive----------------------Local Value----------Master Value
pcre.backtrack_limit-----------100000-----------------100000
pcre.recursion_limit------------100000-----------------100000
16.02.2012, 16:30
Ответить

1234ru

Хм.. Странно. Довольно новая версия (новее, чем, по крайней мере, одна из тех, которые у меня работают успешно - вообще 6,6.06 от 2006 года), хотя 2008 год можно и обновить (лучше всего - PHP до 5.2.16, там вроде никаких критичных несовместимостей нет, не 5.3 все-таки).
Надо более тонкие отличия в версиях библиотек поискать..
То, что не убивает нас, делает нас инвалидами.
16.02.2012, 20:40
Ответить
NO USERPIC

Virtus-pro

На моем хостинге 5.3.9 , работает отлично. Это у друга такие ошибки лезут, просто он не сможет обновить пхп. Не его хост. Вот думал поможите.

Ну все равно спасибо за информацию
17.02.2012, 20:19
Ответить

1234ru

Честно говоря, просто не думал, что такие проблемы могут возникать на сравнительно новых версиях PHP.
Если это будет повторяться - придется мне переписать без именованных подмасок (там просто регулярное выражение сложное, без именования читается плохо).
Другу скажите, можем специально для него сделать версию без них, но тогда при обновлении шаблонизатора (если вдруг такое понадобится) придется править код руками.
То, что не убивает нас, делает нас инвалидами.
17.02.2012, 21:46
Ответить
NO USERPIC

Virtus-pro

Я буду очень благодарен если сделаете такую версию. А что там придется править руками? Если честно там всего 4-5 циклов во всем движке. Думаю не сложно будет поменять

Можно ваш icq ? Поподробнее обсудить хотелось бы
18.02.2012, 11:35
Ответить

1234ru

Ок, версию сделаю (постараюсь сегодня-завтра).
Да, поменять будет не очень сложно, просто тогда потребуется понаблюдать за работой - вдруг я где ошибусь при исправлении.
ICQ не пользуюсь, дам почту.
То, что не убивает нас, делает нас инвалидами.
20.02.2012, 08:28
Ответить

1234ru

Сделал версию без именованных подшаблонов (0.1.24b): http://webew.ru/f/nP0PVBWw.php (upd от 14.03.2012: есть более новая версия - 0.1.27).
Там изменены методы parse_if() и parse_vars_templates_modules().


Сейчас я вспомнил главную причину, почему использовались именованные подмаски: когда в рег. выражении есть условия, действующие на целые маски (вида '(...)?'), в массиве совпавших строк может меняться нумерация (по крайней мере, у меня сложилось такое впечатление).
Например, если в шаблоне вида /a(b)?(с)?/ не совпадут ни (b), ни (c), то в массиве совпадений второй и третий элементы ([1] и [2] - нумерация с нуля) будут вообще отсутствовать, тогда как если не совпадет (b), но совпадет (c), и [1], и [2] будут в наличии, при этом в [1] будет пустая строка.

Впрочем, нумерация подмасок, вроде бы, не меняется (т.е. соответствующий элемент в массиве либо вообще отсутствует, либо всегда имеет один и тот же номер ли).


В любом случае, если что-то будет работать не так, как ожидалось, нужно проверить, какой результат дает следующий код:

<?php
$pattern = '/(a)(b)?(c)?/';
$string = 'ac';
preg_match($pattern, $string, $matches);
print_r($matches);
?>


Должно быть:

Array
(
    [0] => ac
    [1] => a
    [2] =>
    [3] => c
)
То, что не убивает нас, делает нас инвалидами.
20.02.2012, 13:50
Ответить
NO USERPIC

sogdianacha

Здравствуйте,
А как быть, если в шаблоне smarty нужно использовать условие. Например, есть такой код:
<code lang="php">
<?
{foreach from=$show_meta_cat item=i}
<META content='{$ismkey}' name=Keywords>
<META content='Добавить компанию в {$ismdesc}' name=Description>
{foreachelse}
<META content='{$description}, {$name}' name=Keywords>
<META content='{$name}|{$descr}|{$description}' name=Description>

{/foreach}
?>
</code>
Но тут нужно добавить 3 условие: Например, на главной странице должна отображаться другие мета описания и ключевые слова. Эти, которые выше, они по применимы для страниц категорий и объявлений. Нужно сделать такое условия + еще условие что если пользователь на главной, сформировать мету такое, а если в категории, то название категории, а если пользователь на странице объявления, то генерировать из объявления мету.

Помогите мне решить эту задачу. Спасибо.
02.03.2012, 03:37
Ответить

1234ru

А покажите массив, который Вы скармливаете шаблонизатору (print_r) - (я в smarty не силен, поэтому не вполне понимаю, что шаблон означает)
То, что не убивает нас, делает нас инвалидами.
02.03.2012, 13:52
Ответить
NO USERPIC

axules

Ну ты вообще прикалолся =) а почему бы тебе не запостить там где люди постят про СМАРТИ? =) Тут ты врядли найдешь такую посещаемость пользователей смарти, которые могли бы тебе помочь =)
13.03.2012, 11:54
Ответить
NO USERPIC

axules

Вот еще баг:
в шаблоне ставлю просто двоеточие внутри цикла, например (реальный пример):
{%**}
: : : <br>
{**%}

вроде бы должен быть хтмл вида
: : :
: : :
: : :
Но получил:
0. 0. 0.
1. 1. 1.
2. 2. 2.

Версия 1.24б

Я конечно могу сам перебрать код и исправить, но потом будет сложно обновляться у вас =)
13.03.2012, 11:51
Ответить

1234ru

Спасибо за багрепорт!

Вот исправленная версия - websun-0.1.25-alt.php (теперь вместо b называется alt - от "alternative"), в ней такой цикл отрабатывает как положено.

Ссылки на основную версию обновил в головном сообщении.
То, что не убивает нас, делает нас инвалидами.
14.03.2012, 21:55
Ответить
NO USERPIC

sogdianacha

Спасибо, что ответили. Хоть на этом спасибо.
14.03.2012, 01:07
Ответить
NO USERPIC

axules

Увы и ах перестали работать условия сравнения с нулем...
{?*NODAT=0*}

я так полагаю, что теперь отсутствующее значение, пустая строка и фалсе не приравниваются к нулю ...
печаль =(
-------
Я ошибся ... Просто сравнение с нулем не работает ... у меня процедура возвращает 0, а шаблонизатор не может сравнить с нулем ...
-------
А вот и косяк
if (isset($matches[6]) AND $matches[6])
если $matches[6] = 0, то даже в условие не зайдет и не сравнит, вот этот энд вообще ни к чему ...
внутри условия стоит условие
$compare_value = ($matches[6])
? $this->var_value($matches[6])
: FALSE ;
так что самое разумное решение убрать "AND $matches[6]"
тогда все будет шикарно
что я и сделал ... жду обновленную версию с этим исправлением
---
вот мое временное решение (кусок кода из функции function parse_if($matches) )
if (isset($matches[6])) {
$compare_value = ($matches[6])
? $this->var_value($matches[6])
: FALSE ;

switch($matches[5]) {
case '=': $check = ($variable_value == $compare_value); break;
case '>': $check = ($variable_value > $compare_value); break;
case '<': $check = ($variable_value < $compare_value); break;
}
}

Ииии спасибо за отзывчивость разработчику =) Кстати код легко читаем, так что я думаю что каждый может помогать вам модифицировать, то есть не просто ругаться на ошибки, а по возможности предлагать решения!
Еще раз спасибо!
26.03.2012, 11:02
Ответить

1234ru

Верно подмечено (я в таких случаях пишу {?!*NODAT*}, поэтому как-то не замечал).
Точнее, у этого условия были свои предпосылки; я даже хотел было их Вам описать, но что-то на середине описания мне стало лень, и я решил, что это незначительно.
Вот обновленная версия: websun-0.1.26-alt.php. Проверьте, пожалуйста, и отпишитесь - если все нормально, обновлю и основную ветку.

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

Вам отдельное спасибо за внимание к коду. Честно, не думал, что кто-то отважится его читать, особенно регулярные выражения :)
То, что не убивает нас, делает нас инвалидами.
26.03.2012, 15:34
Ответить
NO USERPIC

axules

Я на двух проектах посмотрел - вроде все работает =)
Мне еще предстоит много работы, так что если что не так будет - замечу и обязательно сообщу =) но надеюсь что все будет так как надо =) а то так не люблю сюрпризы такого рода =)
26.03.2012, 21:34
Ответить

1234ru

Cделал alt-версию основной (0.1.27): http://webew.ru/f/PrKZ2372.php

В 0.1.27 добавил скармливание переменной прямо в качестве шаблона (т.е. берется содержимое переменной и обрабатывается как шаблон; столкнулся с задачей, где это было удобным, вот и решил дополнить).
Пользоваться следует так же, как вложенными шаблонами:

{* + >*tpl* *}
{* + *subvar* | >*tpl* *}
{* + *%subvar* | >*tpl* *}
{* + *?subvar* | >*tpl* *}
{* + @module | >*tpl* *}
То, что не убивает нас, делает нас инвалидами.
30.03.2012, 19:46
Ответить
NO USERPIC

triungulin

*Время обработки шаблона составляет в среднем 20-70 мс.*

Доброго времени суток!
попробывал использовать - столкнулся с проблемой. все было нормально, пока не:
беру простой шаблон с перебором в цикле 700 элементов (выпадающий список )
Дак обрабатывается он у меня на коре два дуо 2 гигагерц 2 секунды в версии 0.123 в последней версии 0.1.27 вообще 4!!

как-то можно оптимизировать?
10.04.2012, 15:50
Ответить

1234ru

Что-то многовато для 700 элементов..
Покажите шаблон и пришлите массив данных файлом в виде serialize() - надо посмотреть конкретно.
То, что не убивает нас, делает нас инвалидами.
10.04.2012, 15:57
Ответить
NO USERPIC

triungulin

http://www.box.com/s/2dec1e94e011aa199c2b
10.04.2012, 16:31
Ответить

1234ru

И правда долго (у меня тоже почти четыре секунды работало).
Кое-что поправил, и версия 0.1.29 обрабатывает ваш шаблон меньше чем за десятую секунды.


Кстати, добавил простенький профилировщик (узкое место пришлось отлавливать, так что решил потратить чуть больше времени, добавив тогда уже инструмент). Он распечатывает некоторую статистику по времени выполнения и количеству вызовов функций шаблонизатора.
Чтобы его включить, нужно вызвать шаблонизатор с дополнительным аргументом:

$HTML = websun_parse_template(
        $DATA,
        $template,
        FALSE, // это корневой каталог шаблонизатора
               // когда установлен в FALSE - берется текущий
        TRUE // а это включение профилировщика
    );


Честно говоря, не сильно над ним старался, делает профилировщик только самое необходимое.
Но выпьем за то, чтобы в нем просто не было надобности - чтоб все работало быстро.


А затормозило вот что.
В регулярном выражении подмаска, ловящая имя переменной в if, была слишком нестрогая - [syntax=*text]([^<>=]*) - то есть вообще любой символ, кроме тех, что участвуют в построении конструкции if в шаблоне (изначально я специально так сделал, чтобы дать возможность в именах переменных использовать что угодно; PHP это позволяет, и иногда это теоретически может быть удобно). В результате символьный подкласс отправлялся гулять по всей строке, долго не терпя неудачу - на это и уходило всё время. Эти три-четыре секунды почти полностью уходили на один прогон регулярного выражения для if по развернутому из цикла шаблону (который получался 120 килобайт длиной). Так что я решил идти от строгого, составив символьный класс в явном виде, а не от противного. Если кому-то понадобятся какие-нибудь символы (типа пробелов) - будем добавлять по мере необходимости.
То, что не убивает нас, делает нас инвалидами.
11.04.2012, 00:34
Ответить
NO USERPIC

triungulin

Спасибо за отзывчивость и оперативность! заметно помогло и сложному шаблону из примерно 50 элементов , 0.15 с против 0.5 с уже не так вызывающе....
11.04.2012, 09:55
Ответить

1234ru

Отлично!

Правда, тут неувязочка произошла.. В версии 0.1.28 допущена ошибка, нужно обновить следующей (я в предыдущем своем ответе обновил и в самой статье обновил ссылки).

Взялась ошибка вот откуда: вставлял код для профилирования и в одной из функций нечаянно замочил return :o (ошибка мелкая, нечего сказать )), но на вашем шаблоне этого не видно, потому что она для него не вызывалась).
То, что не убивает нас, делает нас инвалидами.
11.04.2012, 13:39
Ответить
Добавить комментарий
Отображение комментариев: Древовидное | Плоское
© 2007—2012 webew.ru, связаться: x собака webew.ru
Сайт использует Flede и соответствует стандартам WAI-WCAG 1.0 на уровне A.
Rambler's Top100