Шаблонизатор для PHP: часть вторая

Февраль 27, 2008

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

Далее будет рассмотрен шаблонизатор, который использую в текущих проектах. Стоит упомянуть, что я пишу придерживаясь технологии MVC — Modules-Views-Controllers. Эта технология требует полного разделения логики приложения, получения данных и оформления. Но, стоит заметить, что этот подход я немного модифицировал. Как? Покажу на примере :)

Вот пример простого шаблона:

 <table>
<tr>
<td>LBL_HEADER_TAG</td>
<td>{select->news_header_tag}</td>
</tr>
<tr>
<td>LBL_SUBHEADER_TAG</td>
<td>{select->news_subheader_tag}</td>
</tr>

<tr>
<td>LBL_SHOW_SUBHEADER</td>
<td>{checkbox->show_show_subheader}</td>
</tr>
<tr>
<td>LBL_DEFAULT_SPACER</td>
<td>{input->news_default_spacer}</td>
</tr>
<tr>
<td>LBL_MORE_BT</td>
<td>{input->news_more_anchor_BT}</td>
</tr>
<tr>
<td>LBL_NEWS_MORE_ANCHOR</td>
<td>{input->news_more_anchor}</td>
</tr>
<tr>
<td>LBL_ARTICLES_EXTENTION</td>
<td>{input->articles_extention}</td>
</tr>
</table>

Мы видим следующие части данного шаблона (представления)

  1. html-код
  2. языковые константы из php (объявленные через define)
  3. макросы, указывающие тип html-элемента (например, {input->articles_extention})

Подумаем, как можно сделать из этого скелета живой код. Откинем сразу бредовую идею eval — по причине концепции MVC.

Если рассмотреть подробнее — поймем, что, по сути, тут выделяется еще одна ветка — Messages — языковые константы, начинающиеся с префикса LBL_. Это и есть небольшая модификация концепции. Для ее включения в стандарты работы было достаточно причин: от гибкости локализации скриптов, до проблем с кодировками самих файлов-локализаций и шаблонов.

Теперь рассмотрим сам код шаблонизатора. Поскольку, он «выдран» из ядра — существует ряд ограничений на его функционирование «отдельно».

В коде встречаются некоторые «посторонние» классы и переменные, необходимость которых требует пояснений.

  1. $_SESSION['admin_panel'] — флаг функционала шаблонизатора. Принимает значения true или false. Показывает шаблонизатору, какой шаблон нужно подгружать: фронтенд или же бэкэнд.
  2. TMPL_DIR, VIEWS_DIR — константы, которые определяют директории шаблона и представлений, соответственно. Определяются в файле автоматического генератора конфигурации системы (относительно путей, хоста, браузера и т.д.)
  3. $_SESSION['page']['js'] — массив имен js-скриптов, которые необходимо примонтировать к html-коду. Заполняется в контроллерах соответствующий компонентов системы (а так же модулях и ботах)
  4. $_SESSION['page']['onload'] — функция js, которая должна быть выполнена при загрузке страницы (применимо и актуально к AJAX)

И, непосредственно сам код шаблонизатора :)

<?php
class template implements Interface_class
{
public  $output;
private $tmpl;
function __construct(){;}
function __destruct(){;}

function load_template()
{    $this->tmpl = (!$_SESSION['admin_panel'])? '/frontend':'/backend';
$this->output = file_get_contents(TMPL_DIR.$this->tmpl.'/index.tmpl');
}

public function mount_template()
{    $this->output = str_replace('TMPL_DIR', TMPL_DIR.$this->tmpl, $this->output);

$pattern = '^{block->(.*?)}^';
preg_match_all($pattern, $this->output, $matches);

foreach ($matches[1] as $key=>$block)
{    $this->output = str_replace('{block->'.$block.'}', $_SESSION['obj']['route']->page_array[$block], $this->output);
}
}

public function mount_js()
{    $this->output     = str_replace('<body','<body '.$_SESSION['page']['onload'],$this->output);
$scripts = '';
if(is_array($_SESSION['page']['js']))
{    $_SESSION['page']['js']    =    array_unique($_SESSION['page']['js']);
}

/// ВОЗМОЖЕН БАГ!!!
if(sizeof($_SESSION['page']['js'])>0)
{    foreach ($_SESSION['page']['js'] as $file)
{    $scripts .= '<script type="text/javascript" src=\''.TMPL_DIR.'/'.$file.'\' charset="utf-8"></script>'."\n\t";
}
}
$this->output = str_replace('{meta->scripts}',$scripts,$this->output);
}

static public function load_view($view_name)
{    return file_get_contents(VIEWS_DIR.'/'.$view_name);
}

static public function mount_constants($template)
{    preg_match_all('!>(LBL_.*?)<!',$template,$matches);
foreach ($matches[1] as $index=>$const_name)
{    $template = str_replace($const_name,constant($const_name),$template);
}
return $template;
}

static public function get_macroses($template)
{    preg_match_all('!{(.*?)->(.*?)}!',$template, $matches);
return $matches;
}
}
?>

Теперь, собственно, пример использования этого «творения» :)

<?php
$_SESSION['obj']['tmpl']     = new template();
$_SESSION['obj']['tmpl'] -> load_template();
$_SESSION['obj']['tmpl'] -> mount_template();
$_SESSION['obj']['tmpl'] -> mount_js();
echo $_SESSION['obj']['tmpl']->output;
?>

Кроме этого, нам необходим класс html-хелпера, который будет заниматься построением форм на основе макросов.

<?php
class helper_forms
{
public function input($name, $value, $type='text')
{    return '<input type="'.$type.'" name="'.$name.'" id="'.$name.'" value="'.addslashes($value).'" />'."\n\t";
}

public function calendar($name, $value, $type='text')
{    return '<input type="'.$type.'" name="'.$name.'" id="'.$name.'" value="'.addslashes($value).'" /> Календарь'."\n\t";
}

public function hidden($name, $value, $type='text')
{    return '<input type="hidden" name="'.$name.'" id="'.$name.'" value="'.addslashes($value).'" />'."\n\t";
}

public function checkbox($name, $value)
{
return '<input type="checkbox" name="'.$name.'" id="'.$name.'" value="'.addslashes($value).'" />'."\n\t";
}

public function textarea($name, $value)
{    $w = '75';
$h = '15';
return '<textarea name="'.$name.'" id="'.$name.'" rows="'.$h.'" cols="'.$w.'"/>'.addslashes($value).'</textarea>'."\n\t";
}

public function select($name,$default_value = '!@#$%^&*()_()*&^%$#@!', $options_array)
{
if(sizeof($options_array))
{    $output = '<select name="'.$name.'" id="'.$name.'" >'."\n\t";
foreach ($options_array as $num=>$value)
{    $output.='<option name="'.$num.'" id="'.$num.'">'.$value.'</option>'."\n\t";
}
if(($default_value !='') && (strpos($output,$default_value)))
{    $output = str_replace('<option name="'.$default_value.'"', '<option name="'.$default_value.'" selected ', $output);
}
$output.='</select>'."\n\t";
}
return $output;
}
}
?>

Вот, собственно, и все. Можете критиковать или давать определенные пожелания, которые я постараюсь реализовать.
Стоит отметить, что данный шаблонизатор с успехом обслуживает N-ное количество сайтов, не вызывая особых нареканий ;)

Тем, кто понял о чем речь — код не нужен. Только концепция. А тем, кто не понимает как это работает — снизу есть форма комментария — используйте ее, чтобы задать вопрос.

5 комментариев на “Шаблонизатор для PHP: часть вторая”

  1. Pavel высказал:

    В целом идея очень похожа на мою, даже очень близка, только я с разными языками не заморачивался, не было необходимости. Но у меня возникает резонный вопрос, как в концепцию MVC вливается класс helper_forms. Помоему не хорошо, что html код инкапсулирован внутрь класса.

  2. jeurey высказал:

    Есть желание устраивать парсинг элементов форм?

    Регулярки — не очень хорошо. Виртуальную машину нужно писать, чтобы обрабатывать «в сырцах».

    Можете предложить мне другую альтернативу? :)

  3. Yaroslav Vorozhko высказал:

    > Можете предложить мне другую >альтернативу? :)

    Как насчет и т.д. или PHP уже перестал быть самым быстрым и самым гибким из всех шаблонизаторов :)

  4. RazoR высказал:

    интересно было-бы посмотреть твою реализацию мультиязычности.

    долго уже бьюсь над хорошим решением!

  5. Jeurey высказал:

    > 2Yaroslav Vorozhko

    Нельзя ли выражаться чуток яснее? Если судить по тому, как я понял вопрос — то можно обойтись без виртуальных машит регулярных выражений, а пользоваться first/end-point`ами,

    > 2Razor — мультиязычность, мне, по сути, не очень нужна... У меня с другим проблема была — заказчики, обычно, сидят под виндами. Про UTF они и слышать не хотели... Легче ведь использовать константы, хранить их в одном файле.

    Во-первых, я делаю перекодировку всего 1-го файла, чтобы читалось на виндах.

    Во-вторых, захотели «переименовать» определенные label`ы — открыли и переименовали.

    :)