2
Сен

PHP как шаблонизатор

Posted by rebbort under Программирование

Кто-нибудь задумывался, зачем разделять HTML-код от PHP? Задумывались, наверно, все, кто хоть раз сталкивался с шаблонизаторами или с теми, кто их пропагандирует. Я считаю, что разделять нужно и делать это по очень простой причине: поддерживать проект становится значительно легче:

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

Можно, конечно, привести еще много разных «за» и «против», но на мой вгляд, эти две причин являются основными, особенно для меня.

Другой вопрос: как делать это самое разделение? Обычно при обсуждении данной темы от «нужны или не нужны» шаблонизаторы, народ быстро переключается на как «быстро или медленно» они должны работать, потому как уже есть целая куча разных шаблонизаторов и можно выбирать. Я в свое время начинал работать с шаблонами при помощи шаблонизатора из библиотеки PHPLIB. Работать с ним было просто как два пальца об ас…, но жизнь портило отсутствие условий на проверку существования переменных.

Позже, уже на работе, хотелось перейти на Smarty, но его громоздкость и правила студии (не использовать чужие решения) не позволили это сделать. Пришлось писать свой шаблонизатор. Чтобы долго не долбаться, я начал использовать XSLT шаблоны, так как по возможностям XSLT-шаблонизатор по-моему ничем не уступает Smarty. Но поддержка больших проектов показала, что я круто лоханулся, используя такие шаблоны. Верстальщица просто «зашивалась», когда начинала редактировать шаблон, так как один проставленный не там лишний слеш или запятая ломали страницу сайта наповал. И кроме этого шаблоны на XSLT начинали разрастаться до неприличных размеров из-за конструкций типа:


	…
	<tr>
		<xsl:if test="position() mod 2 = 0">
			<xsl:attribute name="bgcolor">
				<xsl:value-of
				disable-output-escaping="yes"
				select="color" />
			</xsl:attribute>
		</xsl:if>
		<td>
			…
		</td>
	</tr>
	…

хотя все это можно было бы представить компактней, например, так:


	…
	<tr [*IF($k%2==0):*]bgcolor="[*=$color*]"[*ENDIF*]>
		<td>
			…
		</td>
	</tr>
	…

И последнее, что добило в XSLT, это то, что при генерации больших объемов информации время генерации страниц увеличивалось экспоненциально. В общем, промучившись так несколько месяцев и издав на гора 5 сайтов, было решено написать новый шаблонизатор.

Основные требования к шаблонизатору были выдвинуты следующие:

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


Первая мысль, которая пришла мне на ум, после выдвинутых мною же требований, была отказаться от шаблонизатора и пользоваться чистым PHP, но вспомнив про Smarty тут же пришла и вторая: можно откомпилировать шаблон только один раз и использовать его уже потом, как обычный PHP-скрипт. Ностальгия по XSLT заставила запрограммировать возможность хранения нескольких шаблонов в одном файле, на мой взгляд очень удобно. К тому же если немножко поднапрячься, то можно прикрутить еще и возможность вызывать шаблон из шаблона, которые находятся в одном файле.

Сказано сделано. После двух часов писанины и тестинга было получены следующие строчки:


<?php
class Template{
	public $Dir = ''; //Путь к папке с шаблонами

	private $Vars = array(); // Массив с переменными
	private $TmplFile = '';  // Имя компилируемого файла
	private $TmplCache = ''; // Имя откомпилированного файла

	// Конструктор
	function __construct($dir=''){
		//Можно задать произвольную директорию, с шаблонами
		$this->Dir = empty($dir)?$_SERVER['DOCUMENT_ROOT'].’/templates/pages/’:$dir;
	}

	// Функция помещает переменную в общий массив
	// @name - название переменной
	// @value - ее значение
	public function SetVar($name,$value){
		$this->Vars[$name] = $value;
	}

	// Функция выводит все данные из общего массива на страницу.
	// Используется для отладки
	public function PrintVars(){
		print "<pre>";
		print_r($this->Vars);
		print "</pre>";
	}

	// Функция генерации страницы
	// @filename - имя файла с нужным шаблоном
	// @tmplname - имя шаблона внутри файла
	// (один файл с шаблонами может иметь несколько шаблонов, ностальгия по XSLT)
	public function Parse($filename,$tmplname=’root’){
		//Проверка на существование файла
		if(!is_file($this->Dir.$filename)){
			print "Файл {$filename} не является шаблоном или не найден.";
			return;
		}
		//Запоминаем имя файла с шаблонами
		$this->TmplFile = $this->Dir.$filename;
		//Запоминаем имя откомпилированного файла с шаблонами
		$this->TmplCache = $this->Dir.’.cache.’.$filename;

		//Заносим название шаблона в зарезервированную переменную $TEMPLATE
		$TEMPLATE = $tmplname;

		//Создаем переменные из общего массива, чтобы они были видны в шаблоне
		foreach($this->Vars as $k=>$v){
			$$k = $v;
		}

		//Смотрим время последнего обращения к файлу
		$orig_time = fileatime($this->TmplFile);

		//Если существует уже скомпилированная версия файла,
		//то используем ее.
		if(is_file($this->TmplCache)){
			//Смотрим время последнего обращения к откомпилированному файлу
			$cash_time = fileatime($this->TmplCache);
			//Если оригинальный файл не изменился уже после того, как он был откомпилирован,
			//то используем откомпилированную версию.
			if($cash_time>$orig_time){
				//Включаем кэширование
				ob_start();
				//Отключаем вывод нотисов и варнингов
				error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);
				//Подключаем шаблон
				include($this->TmplCache);
				//Включаем вывод всех ошибок
				error_reporting(E_ALL);
				//Получаем сгенерированный текст
				$text = ob_get_contents(); ob_end_clean();
				return $text;
			}
		}

		//Если откомпилированный файл не существует или оригинальный файл
		//успел измениться после компиляции, то компилируем файл заново

		//Получаем текст шаблонов
		$text = file_get_contents($this->TmplFile);
		/*
		Заменяем конструкцию типа: [*...*] на <?..?>

		*/
		$text = str_replace(array(’[*','*]‘),array(’<?’,'?>’),$text);

		//Записываем код в файл
		$f = fopen($this->TmplCache,’w');
		fwrite($f,$text);
		fclose($f);

		// Выполняем код и возвращаем сгенерированную страницу
		ob_start();
		error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);
		eval(’?>’.$text.’<?’);
		error_reporting(E_ALL);
		$text = ob_get_contents(); ob_end_clean();
		return $text;
	}
}
?>

Кто внимательно просмотрел код, наверно, заметил, что вся компиляция заключается в простой замене [* … *] на <? … ?>, и, наверно, уже хочет что-нибудь написать в комментарии по этому поводу. На момент написания шаблонизатора такая фича нужна была только, для того, чтобы редактор не подсвечивал PHP код в тексте шаблона и все. Но так как шаблонизатор писался для CMS, то в скрипт были добавлены еще функции компиляции вызовов разных макросов и модулей, которые вызывались из самих шаблонов.

Как пользоваться шаблонизатором.

Пример файла с шаблонами:


[*IF($TEMPLATE=='t_rus'):*]
	[*IF($uid>0):*]Приветствуем, [*=$name*] [*ENDIF*]
	[*FOREACH($item as $v):*]
		…
	[*ENDFOREACH*]
[*ENDIF*]

[*IF($TEMPLATE=='t_eng'):*]
	[*IF($uid>0):*]Hello, [*=$name*] [*ENDIF*]
	[*FOREACH($item as $v):*]
		…
	[*ENDFOREACH*]
[*ENDIF*]

Пример PHP скрипта:


<?php
…
$t = new Template();
if($version=='rus')
	$t->SetVar('name','Денис');
else
	$t->SetVar('name','Mario');

$item = array('1','bla bla bla','432','data');
$t->SetVar('item',$item);

$text = $t->Parse('main.tmpl','t_rus');
…
?>

Последний код думаю объяснять не нужно. Как видите все очень просто. В шаблонах используется тот же PHP код, поэтому все мощности этого языка доступны и в шаблонах.
На этом все.

Понравилась статья?

Случайные посты


Reader's Comments

  1. Василий |

    Простите, вопрос немного не по теме, но что это за конструкция -

    ? Это XPath? XQuery? Куда копать и где искать? Сходу не нашел нигде в нете похожих упоминаний. Спасибо

  2. Василий |

    Конструкция которая меня интересует - приведенный вами компактный вариант раскраски строк таблицы

  3. Денис Тимошенко |

    Это XSLT. Можете познакомиться с этим языком поближе на W3Schools или поискать что-нибудь на русском в Google.

  4. undr |

    $text = str_replace(array(’[*','*]‘),array(”),$text);

    А почему не устраивает в шаблоне?

    foreach($this->Vars as $k=>$v){
    $$k = $v;
    }
    Это для чего? Чем extract не подходит?

    $TEMPLATE = $tmplname;
    Это лишнее, ибо $tmplname тоже виден в шаблоне.

    $text = ob_get_contents(); ob_end_clean(); почему не заменить на $text = ob_get_clean();

  5. Денис Тимошенко |

    2 undr

    Дельные замечания, спасибо. Но это всего лишь косметические исправления.

  6. tenshi |

    http://phpclub.ru/talk/showthread.php?postid=772723#post772723

  7. Роман |

    Слишком сложно((
    Можно так сделать…

    function parse($array, $tpl)
    {
    foreach ($array as $var => $val) {
    $$var = $val;
    }
    $tpl = file_get_contents($tpl);
    $tpl = str_replace(array(’{', ‘}’), array(”), $tpl);
    eval(’?>’ . $tpl . ‘

  8. Роман |

    Что то порезался мой прошлый код. Ну нечего. Вот я довел его до совершенства (хотя совершенства не бывает):

    function parsePage($template, $array = array(”,”))
    {
    extract($array);
    $file = ‘templates/’ . $template . ‘.html’;
    $cache_file = ‘templates/cache/’ . $template . ‘.php’;
    $file_time = fileatime($file);
    if (file_exists($cache_file)) {
    $cache_time = fileatime($cache_file);
    if ($cache_time > $file_time) {
    ob_start();
    include($cache_file);
    $cache = ob_get_clean();
    echo ‘file old’;
    return $cache;
    }
    }
    $html = file_get_contents($file);
    $html = str_replace(array(’[*=', '[*', '*]‘), array(”), $html);
    file_put_contents($cache_file, $html);
    ob_start();
    include($cache_file);
    $cache = ob_get_clean();
    echo ‘file new’;
    return $cache;
    }

    Все благодаря вашей статье, спасибо большое)

  9. Tankoff |

    Здравствуйте! Рылся в инете в поисках шаблонизатора, который не будет напрягать сложным синтаксисом, но даст много возможностей и нашел его на вашем сайте! Я сначала не мог настроить шаблонизатор, а потом разобрался. Считаю что это оптимальный вариант, при высокой функциональности и простоте кода.

  10. Денис Тимошенко |

    Спасибо.

Leave a Reply