"Block is Object" — шаблонный энжайн на PHP

"Block is Object" — это шаблонный энжайн, сочетающий простой синтаксис, гибкость и большое количество возможностей. Энжайн обеспечивает идеальное отделение оформления от исходного кода, язык абсолютно не содержит конструкций языков программирования. Энжайн способствует модульному программированию, так как распечатка каждого блока задаётся отдельным классом. Ниже приведён полный перечень возможностей:

Далее приводятся примеры. Результат работы шаблона генерируется в реальном времени на сайте.

Как распечатать шаблон

Шаблон имеет вид:

<p>Hello, <? name ?>!</p>

Код на PHP:

<?php

$sourceFn = $folderName."/_.bo";
$r = HTML_Template_BO_includeFile($sourceFn, "single-byte", $GLOBALS["tplParam"]["dir"], true);
if ($r[0]) {
	$tpl = $r[1];
	$tpl->printb(array(
		'name' => "Compiled World"
	));
} else {
	?>Error!<?php
}

$r = HTML_Template_BO_interpretFile($sourceFn, "single-byte");
if ($r[0]) {
	$tpl = $r[1];
	$tpl->printb(array(
		'name' => "Interpreted World"
	));
} else {
	?>Error!<?php
}

В результате:

Hello, Compiled World!

Hello, Interpreted World!

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

Как вставить в шаблон значение переменной

Шаблон имеет вид:

Цены снижены на <? percent ?>%!

Код на PHP:

<?php
$tpl->printb(array(
	'percent' => 20
));
?>

В результате:

Цены снижены на 20%!

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

Как повторять html-блок в цикле

Шаблон имеет вид:

<table border="1">
<tr>
<? block ( ?>
<td><? name ?>, <? age ?> лет</td>
<? ) ?>
</tr>
</table>

Код на PHP:

<?php
$users = array
(	array('name' => 'Иван', 'age' => 15)
,	array('name' => 'Василий', 'age' => 18)
,	array('name' => 'Николай', 'age' => 17)
);
$tpl->printb(array(
	'block' => new HTML_Template_BO_RArray($users)
));
?>

В результате:

Иван, 15 лет Василий, 18 лет Николай, 17 лет

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

Как прятать текст

Шаблон имеет вид:

<p>
Окончание акции 12.09.2006<? edit
( ?> <a href="<? url ?>">отредактировать</a>, если вы администратор<? ) ?>.
</p>

Код на PHP:

<?php
$tpl->printb(array(
	'edit' => new HTML_Template_BO_RMaybeArg(true, array("url" => "#1"))
));
$tpl->printb(array(
	'edit' => new HTML_Template_BO_RMaybeArg(false)
));
?>

В результате:

Окончание акции 12.09.2006 отредактировать, если вы администратор.

Окончание акции 12.09.2006.

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

Выбор вариантов оформления

Если при печати строки базы данных необходимо менять её оформление в зависимости от типа записи.

Шаблон имеет вид:

<? users { banned ?>
<p style="color: red"><? name ?>, заблокированный. </p>
<? , moderator ?>
<p style="font-weight: bold;"><? name ?>, модератор, <? posts ?> сообщений.</p>
<? , ordinal ?>
<p><? name ?>, пользователь, <? posts ?> сообщений. </p>
<? } ?>

Код на PHP:

<?php
$users = array
(	array("ordinal", array("name" => "Иван", "posts" => 34))
,	array("moderator", array("name" => "Василий", "posts" => 125))
,	array("banned", array("name" => "Николай", "posts" => 2))
,	array("ordinal", array("name" => "Игорь", "posts" => 54))
,	array("ordinal", array("name" => "Дмитрий", "posts" => 4))
,	array("banned", array("name" => "Сергей", "posts" => 20))
);

$tpl->printb(array
(	"users" => new HTML_Template_BO_RArraySwitch($users)
));
?>

В результате:

Иван, пользователь, 34 сообщений.

Василий, модератор, 125 сообщений.

Николай, заблокированный.

Игорь, пользователь, 54 сообщений.

Дмитрий, пользователь, 4 сообщений.

Сергей, заблокированный.

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

Библиотеки шаблонов

Можно создавать библиотеки шаблонов, содержащие фрагменты html-кода. Эти фрагменты можно затем вставлять в другие шаблоны. Вставку следует делать программно, т. е. фрагмент html надо передать шаблону как одну из переменных. Специальной инструкции include в шаблоне нет.

Шаблон-библиотека должна иметь расширение .lib.bo.

<? { button ?>
<a href="http://www.yandex.ru/cy?base=0&host=beroal.progz.org">
<img src="http://www.yandex.ru/cycounter?beroal.progz.org" width="88" height="31"
alt="Яндекс цитирования" border="0">
</a>
<? , button2 ?>
...
<? }

Другой шаблон, который использует библиотеку:

Это денежка Яндекса: <? button ?>.

Код на PHP:

<?php
$lib = include1($folderName.'/_.lib.bo');
$tpl->printb(array
(	'button' => $lib['button']
));
?>

В результате:

Это денежка Яндекса: Яндекс цитирования .

Конвертеры

Данные в шаблоне можно конвертировать с помощью специальных объектов-конвертеров. Конвертеры являются классами PHP, у которых определён метод convert. Конвертеры должны быть наследованы от HTML_Template_BO_Converter. Можно конвертировать как значения переменных, так и целые HTML-блоки. Можно применить несколько конвертеров последовательно.

Шаблон имеет вид:

<p>(<? upper ( ?>привет<? ) ?>)
&nbsp;(<? dotted ( ?>привет<? ) ?>)
&nbsp;(<? dotted upper ( ?>привет<? ) ?>)</p>
<p>(<? upper name ?>)
&nbsp;(<? dotted name ?>)
&nbsp;(<? dotted upper name ?>)</p>
<p>(<? upper "литерал" ?>)
&nbsp;(<? dotted "литерал" ?>)
&nbsp;(<? dotted upper "литерал" ?>)</p>

Код на PHP:

<?php
class UpperConv extends HTML_Template_BO_Converter
{
	function convert($x)
	{
		return strtoupper(HTML_Template_BO_printToString($x));
	}
}

class DottedConv extends HTML_Template_BO_Converter
{
	function convert($x)
	{
		$s = HTML_Template_BO_printToString($x);
		$s1 = '';
		for($i = 0; $i < strlen($s); $i++) {
			$s1 .= $s[$i].'.';
		}
		return $s1;
	}
}

$tpl->printb(array
(	'upper' => new UpperConv()
,	'dotted' => new DottedConv()
,	'name' => "Дмитрий"
));
?>

В результате:

(привет)  (п.р.и.в.е.т.)  (п.р.и.в.е.т.)

(Дмитрий)  (Д.м.и.т.р.и.й.)  (Д.м.и.т.р.и.й.)

(литерал)  (л.и.т.е.р.а.л.)  (л.и.т.е.р.а.л.)

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

Как вынести блок в отдельный файл

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

Вот файл external.bo с содержимым блока:

Hi, <? name ?>!

Шаблон:

<p>
<? hello admin ?>
</p>

Код на PHP:

<?php
$tpl->printb(array
(	"hello" => new HTML_Template_BO_RMaybeArg(true
	,	array("name" => "Administrator"))
,	"admin" => include1($folderName."/external.bo")
));
?>

В результате:

Hi, Administrator!

Классификация переменных

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

Шаблон имеет вид:

<p>Данные о товаре:
<br/>
<strong><? caption.name ?>:</strong> <? ware.name ?>
<br/>
<strong><? caption.price ?>:</strong> <? ware.price ?>
<br/>
<strong><? caption.quantity ?>:</strong> <? ware.quantity ?>
</p>

Код на PHP:

<?php

$variableA = array
(	"caption" => array
	(	"name" => "Название товара"
	,	"price" => "Цена, руб."
	,	"quantity" => "Количество на складе, шт."
	)
,	"ware" => db_query("select * from wares where id=5")
);
$tpl->printb($variableA);
?>

В результате:

Данные о товаре:
Название товара: ноутбук Acer 3003LC
Цена, руб.: 20000
Количество на складе, шт.: 1

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

Совпадающие имена переменных во вложенных блоках

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

Шаблон имеет вид:

<p>Новости</p>
<? news ( # category ?>
<p><strong><? category:name ?></strong>
	<a href="#">news_category.php?oper=edit&id=<? category:id ?></a>
<ul>
	<? items ( # item ?>
	<li><? item:name ?>
		<a href="#">news_item.php?oper=edit&id=<? item:id ?></a></li>
	<? ) ?>
</ul>
</p>
<? ) ?>

Код на PHP:

<?php
$news = new HTML_Template_BO_RArray(array
(	array
	(	"id" => 1
	,	"name" => "Политика"
	,	"items" => new HTML_Template_BO_RArray(array
		(	array
			(	"id" => 1
			,	"name" => "Аааа"
			)
		,	array
			(	"id" => 2
			,	"name" => "Ббббббб"
			)
		))
	)
,	array
	(	"id" => 2
	,	"name" => "Финансы"
	,	"items" => new HTML_Template_BO_RArray(array
		(	array
			(	"id" => 3
			,	"name" => "Ввввввв"
			)
		,	array
			(	"id" => 4
			,	"name" => "Гггггг"
			)
		))
	)
));
$tpl->printb(array(
	"news" => $news
));
?>

В результате:

Новости

Политика news_category.php?oper=edit&id=1

Финансы news_category.php?oper=edit&id=2

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

Комментарии

Можно вставлять C-шные комментарии /* ... */.

Шаблон имеет вид:

<table border="1">
<tr>
<?/*comment*/block/*comment*/(/*comment*/?>
<td><?/*comment*/name/*comment*/?>, <?age?> лет</td>
<?)?>
</tr>
</table>

Код на PHP:

<?php
$users = array
(	array('name' => 'Иван', 'age' => 15)
,	array('name' => 'Василий', 'age' => 18)
,	array('name' => 'Николай', 'age' => 17)
);
$tpl->printb(array(
	'block' => new HTML_Template_BO_RArray($users)
));
?>

В результате:

Иван, 15 лет Василий, 18 лет Николай, 17 лет

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

Рекурсивный блок

Шаблон имеет вид:

<style>
.a, .b	{
	border-width: 1px;
	border-color: black;
	border-top-style: none;
	border-right-style: none;
	border-left-style: solid;
	margin-left:	10px;
}
.a	{
	border-bottom-style: solid;
}
.b, .c	{
	height:	100%;
}
.b	{
	border-bottom-style: none;
}
</style>
<div>root</div>
<? recursive ( ?>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
    <td style="width: 20px;">
        <div class="a" ><span style="font-size: 65%">&nbsp;</span></div>
        <div class="<? last { false ?>b<? , true ?>c<? } ?>"><img src="spacer.gif" /></div>
    </td>
    <td><div><? name ?></div><? child ?></td>
</tr>
</table>
<? ) ?>

Код на PHP:

<?php

class RRecursivePa
{
	var $block;
	var $data;
	function RRecursivePa($block, $data)
	{
		$this->block = $block;
		$this->data = $data;
	}
	function print_()
	{
		$a = array();
		$last = count($this->data);
		foreach ($this->data as $name => $data1) {
			$last--;
			$this->block->printb(array
			(	"name" => $name
			,	"child" => new RRecursivePa($this->block, $data1)
			,	"last" => new HTML_Template_BO_RBool(0==$last)
			));
		}
	}
}
class RRecursive
{
	var $data;
	function RRecursive($data)
	{
		$this->data = $data;
	}
	function printr($block)
	{
		$pa = new RRecursivePa($block, $this->data);
		$pa->print_();
	}
}
$data = array
(	"aaaaa" => array
	(	"bbbbb" => array()
	,	"cc" => array
		(	"www" => array()
		,	"hhhhhhh" => array()
		)
	)
,	"dddd" => array()
,	"deee" => array
	(	"sssssss" => array
		(	"www" => array()
		,	"llll" => array
		(	"xxxxxxx" => array
			(	"yyyyyyy" => array
				(	"zzzzzzz" => array()
				)
			)
		)
		,	"mmmmmmm" => array()
		,	"nnnnnn" => array()
		)
	)
);
$tpl->printb(array(
	"recursive" => new RRecursive($data)
));
?>

В результате:

root
 
aaaaa
 
bbbbb
 
cc
 
www
 
hhhhhhh
 
dddd
 
deee
 
sssssss
 
www
 
llll
 
xxxxxxx
 
yyyyyyy
 
zzzzzzz
 
mmmmmmm
 
nnnnnn

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

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

Если шаблон простой, то есть он содержит только переменные, он работает так. Блок в шаблоне преобразуется компилятором в объект, имеющий метод printb. Чтобы напечатать блок, мы вызываем метод printb, передавая ему массив со значениями переменных, которые имеются в блоке. Пример.

<? block ( ?>x: <? x ?>; y: <? y ?>; z: <? z ?>;<? ) ?>
$block->printb(array("x" => 1, "y" => 2, "z" => 3));

Что выполняется, если внутри блока находится ещё один блок. В массив переменных добавляется специальный объект, называемый Renderer-ом. При печати блока (того, который внутри другого блока), у Renderer-а вызывается метод printr, которому передаётся блок как объект с методом printb. Пример.

<? block ( ?>x: <? x ?>; внутренний блок: <? inner ( ?>...<? ) ?>; z: <? z ?>;<? ) ?>
class R {
  function printr($block) { $block->printb(array()); }
}
$block->printb(array("x" => 1, "inner" => new R(), "z" => 3));

Есть несколько готовых Renderer-ов, выполняющих типичные операции: разворачивание массива, печать одного из нескольких вариантов текста, текст, который может быть видимым или невидимым.

Ниже приведен исходный код стандартных Renderer-ов.

<?php
function HTML_Template_BO_printToString($p)
{
	flush();
	ob_start();
	HTML_Template_BO_print($p);
	$ret= ob_get_contents();
	ob_end_clean();
	return $ret;
}
class HTML_Template_BO_RSwitch extends HTML_Template_BO_Renderer
{
	var $variableA;
	var $key;
	function HTML_Template_BO_RSwitch($key, $variableA = array())
	{
		$this->variableA = $variableA;
		$this->key = $key;
	}
	function printr($assoc)
	{
		if (isset($assoc[$this->key])) {
			$block= $assoc[$this->key];
			$block->printb($this->variableA);
		}
	}
}
class HTML_Template_BO_RBool extends HTML_Template_BO_RSwitch
{
	function HTML_Template_BO_RBool($key, $variableA = array())
	{
    	parent::HTML_Template_BO_RSwitch($key ? 'true' : 'false'
    	,	$variableA);
	}
}
class HTML_Template_BO_RArray extends HTML_Template_BO_Renderer
{
	var $list;
	function HTML_Template_BO_RArray($list)
	{
		$this->list = $list;
	}
	function printr($block)
	{
		foreach($this->list as $variableA) {
			$block->printb($variableA);
		}
	}
}
class HTML_Template_BO_RArraySwitch extends HTML_Template_BO_Renderer
{
	var $list;
	function HTML_Template_BO_RArraySwitch($list)
	{
		$this->a = $list;
	}
	function printr($assoc)
	{
		foreach($this->a as $x) {
			list($key, $variableA) = $x;
			$assoc[$key]->printb($variableA);
		}
	}
}
class HTML_Template_BO_RArrayR extends HTML_Template_BO_Renderer
{
	var $rendererA;
	function HTML_Template_BO_RArrayR($rendererA)
	{
		$this->rendererA = $rendererA;
	}
	function printr($x)
	{
		foreach($this->rendererA as $renderer) {
			$renderer->printr($x);
		}
	}
}
class HTML_Template_BO_TplToPrintable extends HTML_Template_BO_Printable
{
	var $variableA;
	var $block;
	function HTML_Template_BO_TplToPrintable($block, $variableA)
	{
		$this->variableA = $variableA;
		$this->block = $block;
	}
	function print_()
	{
			$this->block->printb($this->variableA);
	}
}
class HTML_Template_BO_RBlock extends HTML_Template_BO_Renderer
{
	var $variableA;
	function HTML_Template_BO_RBlock($variableA) { $this->variableA = $variableA;	}
	function printr($block)
	{
			$block->printb($this->variableA);
	}
}
class HTML_Template_BO_RMaybe extends HTML_Template_BO_Renderer
{
	var $maybe;
	function HTML_Template_BO_RMaybe($maybe)
	{
		$this->maybe = $maybe;
	}
	function printr($block)
	{
		if ($this->maybe[0]) {
			$block->printb($this->maybe[1]);
		}
	}
}
class HTML_Template_BO_RMaybeArg extends HTML_Template_BO_RMaybe
{
	function HTML_Template_BO_RMaybeArg($cond, $variableA = array())
	{
		parent::HTML_Template_BO_RMaybe(array($cond, $variableA));
	}
}

Синтаксис языка шаблонов

open_tag ::= '<?' (| 'bo')
close_tag ::= '?>'
resursive ::= name
namespace ::= name
variable ::= name
assoc_index ::= name

unidata ::= number | string | list | assoc | converter
data ::= block | unidata
top_data ::= single | unidata
single ::= '(' block ')'
list ::= '[' (|data (',' data)*) ']'
assoc ::= '{' (|assoc_index data (',' assoc_index data)*) '}'
substitute ::= (|namespace ':') variable (| top_data)
converter ::= substitute
printer ::= substitute
block_text ::= text (open_tag printer close_tag text)*
block ::= (|'@' resursive) (|'#' namespace) close_tag block_text open_tag
template ::= block_text | (open_tag top_data close_tag)

name — имя, содержит буквы, цифры и подчёркивание и должно начинаться с буквы или подчёркивания (регулярное выражение для имени: [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\.]*).

удаленная проверка сервера uptime российских хостеров