Шаблонизатор для 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>
Мы видим следующие части данного шаблона (представления)
- html-код
- языковые константы из php (объявленные через define)
- макросы, указывающие тип html-элемента (например, {input->articles_extention})
Подумаем, как можно сделать из этого скелета живой код. Откинем сразу бредовую идею eval — по причине концепции MVC.
Если рассмотреть подробнее — поймем, что, по сути, тут выделяется еще одна ветка — Messages — языковые константы, начинающиеся с префикса LBL_. Это и есть небольшая модификация концепции. Для ее включения в стандарты работы было достаточно причин: от гибкости локализации скриптов, до проблем с кодировками самих файлов-локализаций и шаблонов.
Теперь рассмотрим сам код шаблонизатора. Поскольку, он «выдран» из ядра — существует ряд ограничений на его функционирование «отдельно».
В коде встречаются некоторые «посторонние» классы и переменные, необходимость которых требует пояснений.
- $_SESSION['admin_panel'] — флаг функционала шаблонизатора. Принимает значения true или false. Показывает шаблонизатору, какой шаблон нужно подгружать: фронтенд или же бэкэнд.
- TMPL_DIR, VIEWS_DIR — константы, которые определяют директории шаблона и представлений, соответственно. Определяются в файле автоматического генератора конфигурации системы (относительно путей, хоста, браузера и т.д.)
- $_SESSION['page']['js'] — массив имен js-скриптов, которые необходимо примонтировать к html-коду. Заполняется в контроллерах соответствующий компонентов системы (а так же модулях и ботах)
- $_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-ное количество сайтов, не вызывая особых нареканий
Тем, кто понял о чем речь — код не нужен. Только концепция. А тем, кто не понимает как это работает — снизу есть форма комментария — используйте ее, чтобы задать вопрос.
Февраль 27, 2008 в 23:22
В целом идея очень похожа на мою, даже очень близка, только я с разными языками не заморачивался, не было необходимости. Но у меня возникает резонный вопрос, как в концепцию MVC вливается класс helper_forms. Помоему не хорошо, что html код инкапсулирован внутрь класса.
Февраль 28, 2008 в 00:23
Есть желание устраивать парсинг элементов форм?
Регулярки — не очень хорошо. Виртуальную машину нужно писать, чтобы обрабатывать «в сырцах».
Можете предложить мне другую альтернативу?
Март 11, 2008 в 18:22
> Можете предложить мне другую >альтернативу?
Как насчет и т.д. или PHP уже перестал быть самым быстрым и самым гибким из всех шаблонизаторов
Март 11, 2008 в 20:32
интересно было-бы посмотреть твою реализацию мультиязычности.
долго уже бьюсь над хорошим решением!
Март 11, 2008 в 21:40
> 2Yaroslav Vorozhko
Нельзя ли выражаться чуток яснее? Если судить по тому, как я понял вопрос — то можно обойтись без виртуальных машит регулярных выражений, а пользоваться first/end-point`ами,
> 2Razor — мультиязычность, мне, по сути, не очень нужна... У меня с другим проблема была — заказчики, обычно, сидят под виндами. Про UTF они и слышать не хотели... Легче ведь использовать константы, хранить их в одном файле.
Во-первых, я делаю перекодировку всего 1-го файла, чтобы читалось на виндах.
Во-вторых, захотели «переименовать» определенные label`ы — открыли и переименовали.