Dev, dev and dev. Also, docs, docs and docs.

This commit is contained in:
Ivan Shalganov 2013-02-07 17:37:16 +04:00
parent 36ab6bd08a
commit 06b7fa488b
49 changed files with 2092 additions and 608 deletions

4
.gitignore vendored
View File

@ -1,4 +1,8 @@
.idea
vendor
composer.lock
composer.phar
tests/resources/compile/*
benchmark/compile/*
benchmark/templates/inheritance/smarty
benchmark/templates/inheritance/twig

7
.travis.yml Normal file
View File

@ -0,0 +1,7 @@
language: php
php:
- 5.3
- 5.4
script: phpunit

View File

@ -1,7 +1,9 @@
Aspect Template Engine
================
Aspect PHP Template Engine
==========================
Templates looks like Smarty:
## [About](./docs/about.md) | [Documentation](./docs/main.md) | [Benchmark](./docs/benchmark.md) | [Bugs](https://github.com/bzick/aspect/issues) | [Articles](./docs/articles.md)
Smarty-like syntax:
```smarty
<html>
@ -9,11 +11,13 @@ Templates looks like Smarty:
<title>Aspect</title>
</head>
<body>
{if $user?} {* or {if !empty($user)} *}
<div>User name: {$user.name}</div>
{if $templaters.aspect?}
{var $tpl = $templaters.aspect}
<div>Name: {$tpl.name}</div>
<div>Description: {$tpl.name|truncate:80}</div>
<ul>
{foreach $user.actions as $action}
<li>{$action.name} ({$action.timestamp|gmdate:"Y-m-d H:i:s"})</li>
{foreach $tpl.features as $feature}
<li>{$feature.name} (from {$feature.timestamp|gmdate:"Y-m-d H:i:s"})</li>
{/foreach}
</ul>
{/if}
@ -29,7 +33,7 @@ $aspect = Aspect::factory('./templates', './compiled', Aspect::CHECK_MTIME);
$aspect->display("pages/about.tpl", $data);
```
Fetch template's result^
Get content
```php
<?php
@ -44,5 +48,6 @@ Runtime compilation
$aspect = new Aspect();
$tempate = $aspect->compileCode('Hello {$user.name}! {if $user.email?} Your email: {$user.email} {/if}');
$tempate->display($data);
// or
$content = $tempate->fetch($data);
```

View File

@ -0,0 +1,22 @@
<?php
use MF\Aspect;
require_once __DIR__ . "/../../../lib/Autoload.php";
Aspect::addTemplateDir(__DIR__.'/../templates');
Aspect::setCompileDir(__DIR__.'/../compiled');
$total = 0;
$t = microtime(1);
$tpl = Aspect::compile('syntax.tpl');
$t = microtime(1) - $t;
var_dump("First compile: ".$t);
$t = microtime(1);
$tpl = Aspect::compile('syntax.tpl');
$t = microtime(1) - $t;
var_dump("Second compile: ".$t);
var_dump("Pick memory: ".memory_get_peak_usage());
?>

View File

@ -0,0 +1,23 @@
<?php
require_once "/data/downloads/Smarty3/libs/Smarty.class.php";
$smarty = new Smarty();
$smarty->addTemplateDir(__DIR__.'/../templates');
$smarty->setCompileDir(__DIR__.'/../compiled');
$t = microtime(1);
$tpl = $smarty->createTemplate('syntax.tpl');
/* @var Smarty_Internal_Template $tpl */
$tpl->compileTemplateSource();
$t = microtime(1) - $t;
var_dump("First compile: ".$t);
$t = microtime(1);
$tpl = $smarty->createTemplate('syntax.tpl');
/* @var Smarty_Internal_Template $tpl */
$tpl->compileTemplateSource();
$t = microtime(1) - $t;
var_dump("Second compile: ".$t);
var_dump("Pick memory: ".memory_get_peak_usage());

View File

@ -0,0 +1,87 @@
<?php
class obj {
public $value = "obj.value property";
public $num = 777;
public function method() {
return "object method";
}
public function methodArgs($i, $j, $k, $str, $value) {
return "object method with ars $i, $j, $k, $str, $value";
}
public function __toString() {
return "object";
}
public function getSelf() {
return $this;
}
}
return array(
"title" => "syntax test page",
"user" => array(
"email" => 'bzick@megagroup.ru',
"name" => 'Ivan'
),
"data" => array(
1 => array(
"foo" => "data.1.foo value"
),
2 => 4,
4 => "data.four value",
5 => array(
"bar" => "data.5.baz value",
"baz" => array(
"foo" => "data.5.baz.foo value"
)
),
6 => array(
"foo" => "data.6.foo value"
),
"bar" => "data.bar value",
"barz" => "data.barz value",
"baz_key" => "baz",
"foo" => array(
"bar" => "data.foo.baz value",
"baz" => array(
"foo" => "data.foo.baz.foo value",
"ls" => array(
4 => "data.foo.baz.ls.4 value",
5 => 555
)
)
),
"obj" => new obj(),
"tpl_name" => 'subdir/subtpl'
),
"foo_key" => "foo",
"bar_key" => "bar",
"barz_key" => "barz",
"baz_key" => "baz",
"item" => "some item",
"x" => 1,
"y" => 2,
"tpl_name" => "subtpl",
"contacts" => array(
array(
"foo" => "bar",
"foo1" => "bar1",
"foo2" => "bar2",
),
array(
"baz" => "buh",
"baz1" => "buh1",
"baz2" => "buh2",
)
),
"logged_in" => false
);

View File

@ -0,0 +1,25 @@
<?php
use MF\Aspect;
require_once __DIR__ . "/../../../lib/Autoload.php";
Aspect::addTemplateDir(__DIR__.'/../templates');
Aspect::setCompileDir(__DIR__.'/../compiled');
/*$ct = microtime(1);
$data = Aspect::compile('syntax.tpl',0);
$ct = microtime(1) - $ct;
echo "\n=====================\nCompile: $ct\n";*/
$_data = require_once __DIR__.'/data.php';
$data = Aspect::fetch('syntax.tpl', $_data, 0);
$dt = microtime(1);
$data = Aspect::fetch('syntax.tpl', $_data, 0);
$dt = microtime(1) - $dt;
$data = MF\Misc\Str::strip($data, true);
echo "$data\n====\n".md5($data)."\n=====================\nDisplay: $dt\n";
var_dump("Pick memory: ".memory_get_peak_usage());
?>

View File

@ -0,0 +1,23 @@
<?php
use MF\Aspect;
require_once __DIR__ . "/../../../lib/Autoload.php";
$tpl = require_once __DIR__.'/data.php';
require __DIR__.'/../templates/syntax.php';
require __DIR__.'/../templates/subdir/subtpl.php';
ob_start();
template_syntax($tpl);
$data = ob_get_clean();
$dt = microtime(1);
ob_start();
template_syntax($tpl);
$data = ob_get_clean();
$dt = microtime(1) - $dt;
$data = MF\Misc\Str::strip($data, true);
echo "$data\n====\n".md5($data)."\n=====================\nDisplay: $dt\n";
var_dump("Pick memory: ".memory_get_peak_usage());
?>

View File

@ -0,0 +1,25 @@
<?php
use MF\Aspect;
require_once __DIR__ . "/../../../lib/Autoload.php";
Aspect::setTemplateDir(__DIR__.'/../templates');
Aspect::setCompileDir(__DIR__.'/../compiled');
$ct = microtime(1);
$data = Aspect::compile('simple.tpl',0);
$ct = microtime(1) - $ct;
$_data = array(
"name" => "Ivan",
"email" => "bzick@megagroup.ru"
);
$data = Aspect::fetch('simple.tpl', $_data, 0);
$dt = microtime(1);
$data = Aspect::fetch('simple.tpl', $_data, 0);
$dt = microtime(1) - $dt;
echo "\n=====================\nCompile: $ct\nDisplay: $dt\n";
?>

View File

@ -0,0 +1,29 @@
<?php
require_once "/data/downloads/Smarty3/libs/Smarty.class.php";
$smarty = new Smarty();
$smarty->addTemplateDir(__DIR__.'/../templates');
$smarty->setCompileDir(__DIR__.'/../compiled');
$ct = microtime(1);
$tpl = $smarty->createTemplate('simple.tpl');
/* @var Smarty_Internal_Template $tpl */
$tpl->compileTemplateSource();
$ct = microtime(1) - $ct;
$_data = array(
"name" => "Ivan",
"email" => "bzick@megagroup.ru"
);
$smarty->assign($_data);
$data = $smarty->fetch('simple.tpl');
$t = microtime(1);
$smarty->assign($_data);
$data = $smarty->fetch('simple.tpl');
$dt = microtime(1) - $t;
echo "\n=====================\nCompile: $ct\nDisplay: $dt\n";

View File

@ -0,0 +1,29 @@
<?php
require_once "/data/downloads/Smarty3/libs/Smarty.class.php";
require_once __DIR__ . "/../../../lib/Autoload.php";
$smarty = new Smarty();
$smarty->addTemplateDir(__DIR__.'/../templates');
$smarty->setCompileDir(__DIR__.'/../compiled');
/*$ct = microtime(1);
$tpl = $smarty->createTemplate('syntax.tpl');
/* @var Smarty_Internal_Template $tpl */
/*$tpl->compileTemplateSource();
$ct = microtime(1) - $ct;
echo "\n=====================\nCompile: $ct\n";
*/
$_data = require_once __DIR__.'/data.php';
$smarty->assign($_data);
$data = $smarty->fetch('syntax.tpl');
$t = microtime(1);
$smarty->assign($_data);
$data = $smarty->fetch('syntax.tpl');
$dt = microtime(1) - $t;
$data = MF\Misc\Str::strip($data, true);
echo "$data\n====\n".md5($data)."\n=====================\nDisplay: $dt\n";
var_dump("Pick memory: ".memory_get_peak_usage());

View File

@ -0,0 +1,47 @@
<?php
$a = new ArrayIterator(str_split(str_pad("", 4, "a")));
$_t = null;
$t = microtime(1);
reset($a);
while($v = current($a)) {
$k = key($a);
next($a);
if(key($a) === null) {
var_dump("last");
}
//var_dump($v);
}
print_r("\n\nWhile: ".(microtime(1) - $t)."\n\n");
$t = microtime(1);
$c = count($a);
foreach($a as $k => $v) {
if(!--$c) var_dump("last");
//var_dump($v);
}
print_r("\n\nforeach + count: ".(microtime(1) - $t)."\n\n");
$t = microtime(1);
reset($a);
while(list($k, $v) = each($a)) {
if(key($a) === null) {
var_dump("last");
}
/*next($a);
if(key($a) === null) {
var_dump("last");
}*/
//var_dump($v);
}
print_r("\neach: ".(microtime(1) - $t)."\n\n");
$t = microtime(1);
foreach($a as $k => $v) {
//var_dump($v);
}
print_r("\n\nforeach: ".(microtime(1) - $t)."\n\n");
?>

View File

@ -0,0 +1,94 @@
{*
data.value = "TXT value";
data.arr.dot.4.retval = "Four";
data.arr.dot.5 = "FiVe";
data.arr.i = 4;
data.arr.retval = "Retval key";
data.set->get = "Get props";
data.iam->getName(...) == "return Name";
data.iam->getFormat(...)== "return Format";
data.num = 777;
data.k = 0;
data.ls = array("a" => "lit A", "c" => "lit C", "d" => "lit D");
*}
Hello <b>world</b>!
My Name is {$data.value}...
Yeah
Hello <b>world</b>!
My Name is {$data.arr.dot|json_encode|lower}...
Yeah
Hello <b>world</b>, {$data.value|upper}!
My Name is {$data.arr[dot].5|upper}...
My Name is {$data.arr.dot[ $data.arr.i|round ]."retval"|upper}...
My Name is {$data.arr."retval"|upper}...
Yeah
Hello <b>world</b>!
My Name is {$data.set->get|upper}...
Yeah
Hello <b>world</b>!
My Name is {$data.iam->getName()|upper}...
Your Name is {$data.you->getFormat(1, 0.4, "%dth", 'grade', $data.arr[dot].5|lower)|upper}?
Yeah
Hello <b>world</b>!
{if isset($data.iam) && !empty($data.set->get)}
My Name is {$data.set->get}...
{/if}
Yeah
Hello <b>world</b>!
{if $data.num >= 5 && $data.k++ || foo($data.value) && !bar($data.num) + 3 || $data.k?}
My Name is {$data->user|upper}...
{/if}
Yeah
Hello <b>world</b>!
{foreach from=$data.ls key=k item=e}
My Name is {$e|upper} ({$k})...
{/foreach}
Yeah
Hello <b>world</b>!
{switch $data.num}
{case "dotted"}
dotted lines<br>
{break}
{case 777}
numeric lines<br>
{break}
{case $data[arr]["dot"].4.retval|upper}
lister<br>
{break}
{/switch}
Yeah
Hello <b>world</b>!
{* if !empty($data.num) *}
{if $data.num?}
dotted lines<br>
{elseif $data[arr]["dot"].4.retval|lower}
lister<br>
{/if}
Yeah
Check system variable<br/>
Current timestamp {$aspect.now}...<br/>
$_GET {$aspect.get.item}...<br/>
$_POST {$aspect.post.myval|upper}...<br/>
$_COOKIES {$aspect.cookies["uid"]}...<br/>
$_REQUEST {$aspect.request?}...<br/>
Consts {$data.number|round:$aspect.const.PHP_INT_MAX}...<br/>
Ok
Hello <b>world</b>!
{for from=1 to=$data.ls|count}
<div>Go</div>
{/for}
Yeah

View File

@ -0,0 +1,17 @@
<html>
<head>
<title>Simple template about {$name|upper}</title>
</head>
<body>
{if $name}
My name is {$name}
{else}
I haven't name :(
{/if}
Ok.
My email {$email}. It is great!
</body>
</html>

View File

@ -0,0 +1,18 @@
<?php
function template_subtpl($tpl) {
if($tpl["user"]["name"]) {
echo 'My name is '.$tpl["user"]["name"];
} else {
echo 'I haven\'t name :(';
};
?>
Ok.
My email <?php echo $tpl["user"]["name"].'. It is great!'; ?>
<?php
}
?>

View File

@ -0,0 +1,9 @@
{if $user.name}
My name is {$user.name}
{else}
I haven't name :(
{/if}
Ok.
My email {$user.name}. It is great!

View File

@ -0,0 +1,184 @@
<?php
function template_syntax($tpl) {
?><html>
<head>
<title><?php echo $tpl["title"]; ?></title>
</head>
<body>
Simple manipulations
<?php echo $tpl["item"]; ?>
<?php echo $tpl["data"][4]; ?>
<?php echo $tpl["data"]["bar"]; ?>
<?php echo $tpl["data"]["bar"]; ?>
<?php echo $tpl["data"]["bar"]; ?>
<?php echo $tpl["data"]['bar']; ?>
<?php echo $tpl["data"][ $tpl["bar_key"] ]; ?>
<?php echo $tpl["data"]["obj"]->value; ?>
<?php echo $tpl["data"]["obj"]->method(); ?>
Many other combinations are allowed
<?php echo $tpl["data"]["foo"]["bar"]; ?>
<?php echo $tpl["data"]["foo"][ $tpl["baz_key"] ][ $tpl["foo_key"] ]; ?>
<?php echo $tpl["data"]["foo"][ $tpl["bar_key"] ]; ?>
<?php echo $tpl["data"][ $tpl["foo_key"] ]["bar"]; ?>
<?php echo $tpl["data"][5]["bar"]; ?>
<?php echo $tpl["data"][5][ $tpl["bar_key"] ]; ?>
<?php echo $tpl["data"]["foo"][ $tpl["baz_key"] ]["ls"][4]; ?>
<?php echo $tpl["data"]["obj"]->methodArgs($tpl["baz_key"], 2, 2.3, "some string", $tpl["bar_key"]); ?> <-- passing parameters
Math and embedding tags:
<?php echo $tpl["x"] + $tpl["y"]; ?> // will output the sum of x and y.
<?php echo $tpl["data"][$tpl["x"] + 3]; ?>
<?php echo $tpl["item"] = 4 + 3; ?> // tags within tags
<?php echo $tpl["data"][4] = 4 + 3; ?>
<?php echo $tpl["item"] = "this is message"; ?> // tags within double quoted strings
Short variable assignment:
<?php echo $tpl["item"] = $tpl["y"] + 2; ?>
<?php echo $tpl["item"] = strlen($tpl["bar_key"]); ?> // function in assignment
<?php echo $tpl["item"] = intval(($tpl["x"] + $tpl["y"]) * 3); ?> // as function parameter
<?php echo $tpl["data"]["bar"] = 1; ?> // assign to specific array element
<?php echo $tpl["data"]["foo"]["bar"] = "data.foo.bar: tpl value"; ?>
Smarty "dot" syntax (note: embedded {} are used to address ambiguities):
<?php echo $tpl["data"]["foo"]["baz"]["foo"]; ?>
<?php echo $tpl["data"]["foo"][ $tpl["baz_key"] ]["foo"]; ?>
<?php echo $tpl["data"][$tpl["y"] + 4]["foo"]; ?>
<?php echo $tpl["data"]["foo"][$tpl["data"]["baz_key"]]["foo"]; ?>
Object chaining:
<?php echo $tpl["data"]["obj"]->getSelf($tpl["x"])->method($tpl["y"]); ?>
Direct PHP function access:
<?php echo strlen("hi!"); ?>
<?php
if($tpl["logged_in"]) {
echo 'Welcome, <span style="color: '.$tpl["fontColor"].'">'.$tpl["name"].'</span>';
} else {
echo "hi, ".$tpl["user"]["name"];
}
?>
Embedding Vars in Double Quotes
<?php echo MF\Aspect\Func::mailto(array("address" => $tpl["user"]["email"],"text" => "test ".$tpl["item"]." test"), $tpl).
MF\Aspect\Func::mailto(array("address" => $tpl["user"]["email"],"text" => "test ".$tpl["foo_key"]." test"), $tpl).
MF\Aspect\Func::mailto(array("address" => $tpl["user"]["email"],"text" => "test ".($tpl["data"][4])." test"), $tpl).
MF\Aspect\Func::mailto(array("address" => $tpl["user"]["email"],"text" => "test ".$tpl["item"].".bar test"), $tpl).
MF\Aspect\Func::mailto(array("address" => $tpl["user"]["email"],"text" => 'test {$data.barz} test'), $tpl).
MF\Aspect\Func::mailto(array("address" => $tpl["user"]["email"],"text" => "test ".($tpl["data"]["barz"])." test"), $tpl).
MF\Aspect\Func::mailto(array("address" => $tpl["user"]["email"],"text" => strtoupper("test ".($tpl["data"]["barz"])." test")), $tpl).
MF\Aspect\Func::mailto(array("address" => $tpl["user"]["email"],"text" => "test ".(strtoupper($tpl["data"]["barz"]))." test"), $tpl); ?>
will replace $tpl_name with value
<?php template_subtpl($tpl); ?>
does NOT replace $tpl_name
<?php echo MF\Aspect\Func::mailto(array("address" => $tpl["user"]["email"],"text" => "one,two"), $tpl); ?>
<?php template_subtpl($tpl); ?>
Math
some more complicated examples
<?php echo $tpl["data"][2] - $tpl["data"]["obj"]->num * !$tpl["data"]["foo"]["baz"]["ls"][4] - 3 * 7 % $tpl["data"][2]; ?>
<?php
if($tpl["data"][2] - $tpl["data"]["obj"]->num * $tpl["data"]["foo"]["baz"]["ls"][4] - 3 * 7 % $tpl["data"][2]) {
echo MF\Misc\Str::truncate($tpl["data"]["barz"], "".($tpl["data"][2] / 2 - 1)."")."\n".
MF\Misc\Str::truncate($tpl["data"]["barz"], ($tpl["data"][2] / 2 - 1));
}
?>
Escaping Smarty Parsing
<script>
// the following braces are ignored by Smarty
// since they are surrounded by whitespace
function foobar() {
alert('foobar!');
}
// this one will need literal escapement
<?php ?>
function bazzy() {alert('foobar!');}
</script>
name: <?php echo $tpl["user"]["name"]; ?><br />
email: <?php echo $tpl["user"]["email"]; ?><br />
Modifier examples
apply modifier to a variable
<?php echo strtoupper($tpl["user"]["name"]); ?>
modifier with parameters
<?php echo MF\Misc\Str::truncate($tpl["user"]["name"], 40, "..."); ?>
apply modifier to a function parameter
<?php echo MF\Aspect\Func::mailto(array("address" => $tpl["user"]["email"],"text" => strtoupper($tpl["user"]["name"])), $tpl); ?>
with parameters
<?php echo MF\Aspect\Func::mailto(array("address" => $tpl["user"]["email"],"text" => MF\Misc\Str::truncate($tpl["user"]["name"], 40, "...")), $tpl); ?>
apply modifier to literal string
using date_format to format the current date
<?php echo MF\Aspect\Modifier::dateFormat(time(), "%Y/%m/%d"); ?>
apply modifier to a custom function
Foreach
<?php
if($tpl["contacts"]) {
foreach($tpl["contacts"] as $tpl["contact"]) {
if($tpl["contact"]) {
foreach($tpl["contact"] as $tpl["key"] => $tpl["value"]) {
echo $tpl["key"].": ".$tpl["value"]." ";
}
} else {
echo "no items";
}
}
}
?>
If condition
<?php
if(isset($tpl["user"]["name"]) && $tpl["user"]["name"] == 'yandex') {
echo "do something";
} elseif($tpl["user"]["name"] == $tpl["data"]["foo"]["bar"]) {
echo "do something2";
}
?>
<?php
if(is_array($tpl["data"]["foo"]) && count($tpl["data"]["foo"]) > 0) {
echo "do a foreach loop";
}
?>
</body>
</html>
<?php
}
?>

View File

@ -0,0 +1,179 @@
<html>
<head>
<title>{$title}</title>
</head>
<body>
{* this is a comment *}
Simple manipulations
{$item}
{$data[4]}
{$data.bar}
{*$data[bar]*}
{$data["bar"]}
{$data['bar']}
{$data.$bar_key}
{$data.obj->value}
{$data.obj->method()}
Many other combinations are allowed
{$data.foo.bar}
{$data.foo.$baz_key.$foo_key}
{$data.foo.$bar_key}
{$data.$foo_key.bar}
{$data[5].bar}
{$data[5].$bar_key}
{$data.foo.$baz_key.ls[4]}
{$data.obj->methodArgs($baz_key, 2, 2.3, "some string", $bar_key)} <-- passing parameters
{*"foo"*}
Math and embedding tags:
{$x+$y} // will output the sum of x and y.
{$data[$x+3]}
{$item=4+3} // tags within tags
{$data[4]=4+3}
{$item="this is message"} // tags within double quoted strings
Short variable assignment:
{$item=$y+2}
{$item = strlen($bar_key)} // function in assignment
{$item = intval( ($x+$y)*3 )} // as function parameter
{$data.bar=1} // assign to specific array element
{$data.foo.bar="data.foo.bar: tpl value"}
Smarty "dot" syntax (note: embedded {} are used to address ambiguities):
{$data.foo.baz.foo}
{$data.foo.$baz_key.foo}
{$data[$y+4].foo}
{$data.foo[$data.baz_key].foo}
Object chaining:
{$data.obj->getSelf($x)->method($y)}
Direct PHP function access:
{strlen("hi!")}
{if $logged_in}
Welcome, <span style="color:{$fontColor}">{$name}!</span>
{else}
hi, {$user.name}
{/if}
Embedding Vars in Double Quotes
{mailto address=$user.email text="test $item test"}
{mailto address=$user.email text="test $foo_key test"}
{mailto address=$user.email text="test {$data[4]} test"}
{*mailto address=$user.email text="test {$data[barz]} test"*}
{mailto address=$user.email text="test $item.bar test"}
{mailto address=$user.email text='test {$data.barz} test'}
{mailto address=$user.email text="test {$data.barz} test"}
{mailto address=$user.email text="test {$data.barz} test"|upper}
{mailto address=$user.email text="test {$data.barz|upper} test"}
will replace $tpl_name with value
{include file="subdir/$tpl_name.tpl"}
does NOT replace $tpl_name
{mailto address=$user.email text="one,two"}
{include file="{$data.tpl_name}.tpl"}
Math
some more complicated examples
{$data[2] - $data.obj->num * !$data.foo.baz.ls[4] - 3 * 7 % $data[2]}
{if $data[2] - $data.obj->num * $data.foo.baz.ls[4] - 3 * 7 % $data[2]}
{$data.barz|truncate:"{$data[2]/2-1}"}
{$data.barz|truncate:($data[2]/2-1)}
{/if}
Escaping Smarty Parsing
<script>
// the following braces are ignored by Smarty
// since they are surrounded by whitespace
function foobar() {
alert('foobar!');
}
// this one will need literal escapement
{literal}
function bazzy() {alert('foobar!');}
{/literal}
</script>
name: {$user.name}<br />
email: {$user.email}<br />
Modifier examples
apply modifier to a variable
{$user.name|upper}
modifier with parameters
{$user.name|truncate:40:"..."}
apply modifier to a function parameter
{mailto address=$user.email text=$user.name|upper}
with parameters
{mailto address=$user.email text=$user.name|truncate:40:"..."}
apply modifier to literal string
{*"foobar"|upper*}
using date_format to format the current date
{$smarty.now|date_format:"%Y/%m/%d"}
apply modifier to a custom function
{*mailto|upper address="smarty@example.com"*}
Foreach
{foreach $contacts as $contact}
{foreach $contact as $key => $value}
{$key}: {$value}
{foreachelse}
no items
{/foreach}
{/foreach}
If condition
{if isset($user.name) && $user.name == 'yandex'}
do something
{elseif $user.name == $data.foo.bar}
do something2
{/if}
{*switch $item}
{case 1}
item 1
{break}
{case 2}
item 2
{break}
{default}
on item
{/switch*}
{if is_array($data.foo) && count($data.foo) > 0}
do a foreach loop
{/if}
</body>
</html>

103
benchmark/aspect/test.php Normal file
View File

@ -0,0 +1,103 @@
<?php
use MF\Misc\Tokenizer;
require_once __DIR__ . "/../../lib/Autoload.php";
$tokens = new \MF\Misc\Tokenizer('$data.value[5].dot|json_decode|lower');
$tpl[0] = 'Hello <b>world</b>!
My Name is {$data.value}...
Yeah';
$tpl[1] = 'Hello <b>world</b>!
My Name is {$data.value[label].dot|json_decode|lower|truncate:80:"...":true:null:0.22:$a.keyz|upper}...
Yeah';
$tpl[2] = 'Hello <b>world</b>, {$user.name|upper}!
My Name is {$data.user1.5.$i."return"[ $hello.world|lower ]|upper}...
Yeah';
$tpl[3] = 'Hello <b>world</b>!
My Name is {$data->set->get|upper}...
Yeah';
$tpl[4] = 'Hello <b>world</b>!
My Name is {$iam->getName()|upper}...
Your Name is {$you->getFromat(1, 0.4, "%dth", \'grade\', $global->name.k|lower)|upper}?
Yeah';
$tpl[5] = 'Hello <b>world</b>!
{if isset($data.user) && !empty($data->value)}
My Name is {$data->user|upper}...
{/if}
Yeah';
$tpl[6] = 'Hello <b>world</b>!
{if $data.user >= 5 && $k++ || foo($data->value) && !bar($d) + 3 || $show.go?}
My Name is {$data->user|upper}...
{/if}
Yeah';
$tpl[7] = 'Hello <b>world</b>!
{foreach from=$users."list" key=k item=e}
My Name is {$e|upper} ({$k})...
{/foreach}
Yeah';
$tpl[8] = 'Hello <b>world</b>!
{switch $data->enum}
{case "dotted"}
dotted lines<br>
{break}
{case 7}
numeric lines<br>
{break}
{case $list|truncate}
lister<br>
{break}
{/switch}
Yeah';
$tpl[9] = 'Hello <b>world</b>!
{if $data->enum?}
dotted lines<br>
{elseif $list|truncate}
lister<br>
{/if}
Yeah';
$tpl[10] = 'Check system variable<br/>
Current timestamp {$aspect.now}...<br/>
$_GET {$aspect.get.item}...<br/>
$_POST {$aspect.post.myval|upper}...<br/>
$_COOKIES {$aspect.cookies["uid"]}...<br/>
$_REQUEST {$aspect.request?}...<br/>
Consts {$number|round:$aspect.const.PHP_INT_MAX|}...<br/>
Ok';
$tpl[11] = 'Hello <b>world</b>!
{for from=1 to=$e|count}
<div>Go</div>
{/for}
Yeah';
$tpl_ = '
Hello <b>world</b>!
My Name is {$data|upper}...
<script>$.dotted({
items: "sorted"
});</script> {* comment *}
I have
<ul>
{foreach $items as $key => $item}
<li>{$item}</li>
{/foreach}
</ul>
';
$template = new MF\Aspect\Template($tpl[7], 'my.tpl');
//var_dump(Tokenizer::decode('case 6:'));
//exit;
echo "\n".$template->getBody()."\n";
//var_dump($template->fetch(array("data" => array("value" => "Yohoho"))));
?>

View File

@ -1,4 +1,6 @@
<?php
require_once __DIR__.'/scripts/bootstrap.php';
exec("rm -rf ".__DIR__."/compile/*");
echo "Smarty3 vs Twig vs Aspect\n\n";
@ -7,14 +9,23 @@ passthru("php ".__DIR__."/templates/inheritance/smarty.gen.php");
passthru("php ".__DIR__."/templates/inheritance/twig.gen.php");
echo "Done\n";
echo "Testing large output...\n";
passthru("php ".__DIR__."/templates/echo.php");
echo "Testing a lot output...\n";
Benchmark::runs("smarty3", 'echo/smarty.tpl', __DIR__.'/templates/echo/data.json');
Benchmark::runs("twig", 'echo/twig.tpl', __DIR__.'/templates/echo/data.json');
Benchmark::runs("aspect", 'echo/smarty.tpl', __DIR__.'/templates/echo/data.json');
echo "\nTesting 'foreach' of big array...\n";
passthru("php ".__DIR__."/templates/foreach.php");
Benchmark::runs("smarty3", 'foreach/smarty.tpl', __DIR__.'/templates/foreach/data.json');
Benchmark::runs("twig", 'foreach/twig.tpl', __DIR__.'/templates/foreach/data.json');
Benchmark::runs("aspect", 'foreach/smarty.tpl', __DIR__.'/templates/foreach/data.json');
echo "\nTesting deep 'inheritance'...\n";
passthru("php ".__DIR__."/templates/inheritance.php");
Benchmark::runs("smarty3", 'inheritance/smarty/b100.tpl', __DIR__.'/templates/foreach/data.json');
Benchmark::runs("twig", 'inheritance/twig/b100.tpl', __DIR__.'/templates/foreach/data.json');
Benchmark::runs("aspect", 'inheritance/smarty/b100.tpl', __DIR__.'/templates/foreach/data.json');
echo "\nDone. Cleanup.\n";
passthru("rm -rf ".__DIR__."/compile/*");

View File

@ -0,0 +1,68 @@
<?php
require(__DIR__.'/../../vendor/autoload.php');
class Benchmark {
public static $t = "%8s: %-22s %10.4f sec, %10.1f MiB\n";
public static function smarty3($tpl, $data, $double, $message) {
$smarty = new Smarty();
$smarty->compile_check = false;
$smarty->setTemplateDir(__DIR__.'/../templates');
$smarty->setCompileDir(__DIR__."/../compile/");
if($double) {
$smarty->assign($data);
$smarty->fetch($tpl);
}
$start = microtime(true);
$smarty->assign($data);
$smarty->fetch($tpl);
printf(self::$t, __FUNCTION__, $message, round(microtime(true)-$start, 4), round(memory_get_peak_usage()/1024/1024, 2));
}
public static function twig($tpl, $data, $double, $message) {
Twig_Autoloader::register();
$loader = new Twig_Loader_Filesystem(__DIR__.'/../templates');
$twig = new Twig_Environment($loader, array(
'cache' => __DIR__."/../compile/",
'autoescape' => false,
'auto_reload' => false,
));
if($double) {
$twig->loadTemplate($tpl)->render($data);
}
$start = microtime(true);
$twig->loadTemplate($tpl)->render($data);
printf(self::$t, __FUNCTION__, $message, round(microtime(true)-$start, 4), round(memory_get_peak_usage()/1024/1024, 2));
}
public static function aspect($tpl, $data, $double, $message) {
$aspect = Aspect::factory(__DIR__.'/../templates', __DIR__."/../compile/");
if($double) {
$aspect->fetch($tpl, $data);
}
$start = microtime(true);
$aspect->fetch($tpl, $data);
printf(self::$t, __FUNCTION__, $message, round(microtime(true)-$start, 4), round(memory_get_peak_usage()/1024/1024, 2));
}
public static function run($engine, $template, $data, $double, $message) {
passthru(sprintf("php %s/run.php --engine '%s' --template '%s' --data '%s' --message '%s' %s", __DIR__, $engine, $template, $data, $message, $double ? '--double' : ''));
}
public static function runs($engine, $template, $data) {
self::run($engine, $template, $data, false, '!compiled and !loaded');
self::run($engine, $template, $data, false, 'compiled and !loaded');
self::run($engine, $template, $data, true, 'compiled and loaded');
echo "\n";
}
}

15
benchmark/scripts/run.php Normal file
View File

@ -0,0 +1,15 @@
<?php
$opt = getopt("", array(
"engine:",
"template:",
"data:",
"double",
"message:"
));
require_once __DIR__.'/bootstrap.php';
extract($opt);
Benchmark::$engine($template, json_decode(file_get_contents($data), true), isset($double), $message);

View File

@ -1,8 +1,23 @@
<?php
$data = json_decode(file_get_contents(__DIR__.'/echo/data.json'), true);
//$data = json_decode(file_get_contents(__DIR__.'/echo/data.json'), true);
require_once __DIR__.'/../scripts/bootstrap.php';
exec("rm -rf ".__DIR__."/../compile/*");
echo "A lot outputs...\n";
Benchmark::run("smarty3", 'echo/smarty.tpl', __DIR__.'/echo/data.json', false, '!compiled and !loaded');
Benchmark::run("smarty3", 'echo/smarty.tpl', __DIR__.'/echo/data.json', false, 'compiled and !loaded');
Benchmark::run("smarty3", 'echo/smarty.tpl', __DIR__.'/echo/data.json', true, 'compiled and loaded');
Benchmark::run("twig", 'echo/twig.tpl', __DIR__.'/echo/data.json', false, '!compiled and !loaded');
Benchmark::run("twig", 'echo/twig.tpl', __DIR__.'/echo/data.json', false, 'compiled and !loaded');
Benchmark::run("twig", 'echo/twig.tpl', __DIR__.'/echo/data.json', true, 'compiled and loaded');
Benchmark::run("aspect", 'echo/smarty.tpl', __DIR__.'/echo/data.json', false, '!compiled and !loaded');
Benchmark::run("aspect", 'echo/smarty.tpl', __DIR__.'/echo/data.json', false, 'compiled and !loaded');
Benchmark::run("aspect", 'echo/smarty.tpl', __DIR__.'/echo/data.json', true, 'compiled and loaded');
exit;
require(__DIR__.'/../../vendor/autoload.php');
$smarty = new Smarty();
$smarty->compile_check = false;

View File

@ -5,7 +5,7 @@ exec("rm -rf ".__DIR__."/../compile/*");
require(__DIR__.'/../../vendor/autoload.php');
$smarty = new Smarty();
$smarty->compile_check = false;
$smarty->compile_check = true;
$smarty->setTemplateDir(__DIR__);
$smarty->setCompileDir(__DIR__."/../compile/");

View File

@ -16,7 +16,7 @@ exec("rm -rf ".__DIR__."/../compile/*");
require(__DIR__.'/../../vendor/autoload.php');
$smarty = new Smarty();
$smarty->compile_check = false;
$smarty->compile_check = true;
$smarty->setTemplateDir(__DIR__);
$smarty->setCompileDir(__DIR__."/../compile/");

View File

@ -1,8 +1,8 @@
{
"name": "megagroup/aspect",
"type": "library",
"description": "Aspect - faster templater for PHP",
"keywords": ["aspect", "templater", "templating", "yohoho"],
"description": "Aspect - faster template engine",
"keywords": ["aspect", "templater", "templating", "template", "yohoho"],
"license": "MIT",
"authors": [
{

3
docs/about.md Normal file
View File

@ -0,0 +1,3 @@
About Aspect
============

11
docs/ext/mods.md Normal file
View File

@ -0,0 +1,11 @@
Модификаторы
============
Добавить модификатор:
```php
$aspect->addModifier($modifier, $callback);
```
* `$modifier` - имя модификатора
* `$callback` - строка с именем функции

20
docs/ext/tags.md Normal file
View File

@ -0,0 +1,20 @@
Теги
====
Теги делятся на компилеры и функции.
Компилеры формируют синтаксис языка шаблона, добавляя такой функционал как foreach, if, while и т.д. В то время как функции - обычный вызов некоторой именованной функции
Добавить компилер:
```php
$aspect->addCompiler($compiler, $parser);
```
* `$compiler` - имя модификатора
* `$parser` - функция разбора тега в формате function (MF\Tokenizer $tokens, MF\Aspect\Template $tpl) {}
Добавить блочный компилер:
```php
$aspect->addBlockCompiler($compiler, $parsers, $tags);
```

43
docs/main.md Normal file
View File

@ -0,0 +1,43 @@
Documentation
=============
**Aspect**
* [About](./about.md)
* [Requirements and installation](./install.md)
* [Syntax](./syntax.md)
* [Settings](./settings.md)
* [Callbacks and filters](./callbacks.md)
**Modifiers**
* [upper](./mods/upper.md)
* [lower](./mods/lower.md)
* [date_format](./mods/date_format.md)
* [date](./mods/date.md)
* [truncate](./mods/truncate.md)
* [escape](./mods/escape.md)
* [unescape](./mods/unescape.md)
* [strip](./mods/strip.md)
Also see [allowed functions](./mods/allowed_functions.md).
**Internal tags**
* [var](./tags/var.md)
* [if](./tags/if.md), `elseif` and `else`
* [foreach](./tags/foreach.md), `foreaelse`, `break` and `continue`
* [for](./tags/for.md), `break` and `continue`
* [while](./tags/while.md), `break` and `continue`
* [switch](./tags/switch.md), `case`, `default` and `break`
* [include](./tags/include.md)
* [extends](./tags/extends.md), `use` and `block`
* [capture](./tags/capture.md)
* [filter](./tags/filter.md)
* [ignore](./tags/ignore.md)
**Extend Aspect**
* [Add tags](./ext/tags.md)
* [Add modificators](./ext/mods.md)
* [Add template provider](./ext/provider.md)
* [Parsing](./ext/parsing.md)

48
docs/settings.md Normal file
View File

@ -0,0 +1,48 @@
Настройка
=========
### Параметры
#### Исходные шаблоны
Добавить папку с шаблонами:
```php
$aspect->addTemplateDir($dir);
```
Шаблонизатор последовательно будет перебирать папки и искать указанный шаблон.
#### Сборки шаблонов
Задаёт папку в которую будут сохранятся преобразованные в PHP шаблоны
```php
$aspect->setCompileDir($dir);
```
#### Опции
```php
$aspect->setOptions($options);
```
Массив `'option_name' => boolean` (если ключ не указан автоматически задаётся false)
* **disable_methods**, `boolean`, запретить вызов методов у объектов
* **disable_native_funcs**, `boolean`, запретить использование PHP функций, кроме разрешенных
* **disable_set_vars**, `boolean`, запретить изменять или задавать переменные
* **include_sources**, `boolean`, вставлять исходный код шаблона в его сборку
* **compile_check**, `boolean`, сравнивать mtime у исходного шаблона и его сборки. При изменении исходного шаблона будет производится его пересборка (замедляет работу шаблонизатора).
* **force_compile**, `boolean`, пересобирать шаблон при каждом вызове (сильно замедляет работу шаблонизатора).
* **force_include**, `boolean`.
или битовая маска из флагов:
* `Aspect::DENY_METHODS` то же что и **disable_methods**
* `Aspect::DENY_INLINE_FUNCS` то же что и **disable_native_funcs**
* `Aspect::DENY_SET_VARS` то же что и **disable_set_vars**
* `Aspect::INCLUDE_SOURCES` то же что и **include_sources**
* `Aspect::CHECK_MTIME` то же что и **compile_check**
* `Aspect::FORCE_COMPILE` то же что и **force_compile**
* `Aspect::FORCE_INCLUDE` то же что и **force_include**

View File

@ -195,81 +195,3 @@
})('test');
</script>
```
Настройка
=========
### Параметры
#### Исходные шаблоны
Добавить папку с шаблонами:
```php
$aspect->addTemplateDir($dir);
```
Шаблонизатор последовательно будет перебирать папки и искать указанный шаблон.
#### Сборки шаблонов
Задаёт папку в которую будут сохранятся преобразованные в PHP шаблоны
```php
$aspect->setCompileDir($dir);
```
#### Опции
```php
$aspect->setOptions($options);
```
Массив `'option_name' => boolean` (если ключ не указан автоматически задаётся false)
* **disable_methods**, `boolean`, запретить вызов методов у объектов
* **disable_native_funcs**, `boolean`, запретить использование PHP функций, кроме разрешенных
* **disable_set_vars**, `boolean`, запретить изменять или задавать переменные
* **include_sources**, `boolean`, вставлять исходный код шаблона в его сборку
* **compile_check**, `boolean`, сравнивать mtime у исходного шаблона и его сборки. При изменении исходного шаблона будет производится его пересборка (замедляет работу шаблонизатора).
* **force_compile**, `boolean`, пересобирать шаблон при каждом вызове (сильно замедляет работу шаблонизатора).
или битовая маска из флагов:
* `Aspect::DENY_METHODS` то же что и **disable_methods**
* `Aspect::DENY_INLINE_FUNCS` то же что и **disable_native_funcs**
* `Aspect::DENY_SET_VARS` то же что и **disable_set_vars**
* `Aspect::INCLUDE_SOURCES` то же что и **include_sources**
* `Aspect::CHECK_MTIME` то же что и **compile_check**
* `Aspect::FORCE_COMPILE` то же что и **force_compile**
### Модификаторы
Добавить модификатор:
```php
$aspect->addModifier($modifier, $callback);
```
* `$modifier` - имя модификатора
* `$callback` - строка с именем функции
### Теги
Теги делятся на компилеры и функции.
Компилеры формируют синтаксис языка шаблона, добавляя такой функционал как foreach, if, while и т.д. В то время как функции - обычный вызов некоторой именованной функции
Добавить компилер:
```php
$aspect->addCompiler($compiler, $parser);
```
* `$compiler` - имя модификатора
* `$parser` - функция разбора тега в формате function (MF\Tokenizer $tokens, MF\Aspect\Template $tpl) {}
Добавить блочный компилер:
```php
$aspect->addBlockCompiler($compiler, $parsers, $tags);
```

View File

@ -1,10 +1,12 @@
<?php
use Aspect\Template;
use Aspect\Template,
Aspect\ProviderInterface;
/**
* Templater
* Aspect Template Engine
*/
class Aspect {
const VERSION = 1.0;
const INLINE_COMPILER = 1;
const BLOCK_COMPILER = 2;
@ -12,48 +14,55 @@ class Aspect {
const BLOCK_FUNCTION = 4;
const MODIFIER = 5;
const DENY_METHODS = 128;
const DENY_INLINE_FUNCS = 256;
const DENY_SET_VARS = 512;
const DENY_METHODS = 0x10;
const DENY_INLINE_FUNCS = 0x20;
const FORCE_INCLUDE = 0x40;
const INCLUDE_SOURCES = 1024;
const CHECK_MTIME = 0x80;
const FORCE_COMPILE = 0xF0;
const CHECK_MTIME = 2048;
const FORCE_COMPILE = 4096;
const DEFAULT_CLOSE_COMPILER = 'Aspect\Compiler::stdClose';
const DEFAULT_FUNC_PARSER = 'Aspect\Compiler::stdFuncParser';
const DEFAULT_FUNC_OPEN = 'Aspect\Compiler::stdFuncOpen';
const DEFAULT_FUNC_CLOSE = 'Aspect\Compiler::stdFuncOpen';
const SMART_FUNC_PARSER = 'Aspect\Compiler::smartFuncParser';
/**
* @var array list of possible options, as associative array
* @var array of possible options, as associative array
* @see setOptions, addOptions, delOptions
*/
private static $_option_list = array(
"disable_methods" => self::DENY_METHODS,
"disable_native_funcs" => self::DENY_INLINE_FUNCS,
"disable_set_vars" => self::DENY_SET_VARS,
"include_sources" => self::INCLUDE_SOURCES,
"force_compile" => self::FORCE_COMPILE,
"compile_check" => self::CHECK_MTIME,
"force_include" => self::FORCE_INCLUDE,
);
/**
* Default options for functions
* @var array
*/
private static $_actions_defaults = array(
self::BLOCK_FUNCTION => array(
'type' => self::BLOCK_FUNCTION,
'open' => 'MF\Aspect\Compiler::stdFuncOpen',
'close' => 'MF\Aspect\Compiler::stdFuncClose',
'open' => self::DEFAULT_FUNC_OPEN,
'close' => self::DEFAULT_FUNC_CLOSE,
'function' => null,
),
self::INLINE_FUNCTION => array(
'type' => self::INLINE_FUNCTION,
'parser' => 'MF\Aspect\Compiler::stdFuncParser',
'parser' => self::DEFAULT_FUNC_PARSER,
'function' => null,
),
self::INLINE_FUNCTION => array(
self::INLINE_COMPILER => array(
'type' => self::INLINE_COMPILER,
'open' => null,
'close' => 'MF\Aspect\Compiler::stdClose',
'close' => self::DEFAULT_CLOSE_COMPILER,
'tags' => array(),
'float_tags' => array()
),
self::BLOCK_FUNCTION => array(
self::BLOCK_COMPILER => array(
'type' => self::BLOCK_COMPILER,
'open' => null,
'close' => null,
@ -62,7 +71,6 @@ class Aspect {
)
);
public $blocks = array();
/**
* @var array Templates storage
*/
@ -81,49 +89,50 @@ class Aspect {
*/
protected $_options = 0;
/**
* Modifiers loader
* @var callable
*/
protected $_loader_mod;
/**
* Functions loader
* @var callable
*/
protected $_loader_func;
protected $_on_pre_cmp = array();
protected $_on_cmp = array();
protected $_on_post_cmp = array();
/**
* @var array list of modifiers
* @var Aspect\Provider
*/
private $_provider;
/**
* @var array of Aspect\ProviderInterface
*/
protected $_providers = array();
/**
* @var array of modifiers [modifier_name => callable]
*/
protected $_modifiers = array(
"upper" => 'strtoupper',
"lower" => 'strtolower',
"nl2br" => 'nl2br',
"date_format" => 'Aspect\Modifier::dateFormat',
"date" => 'Aspect\Modifier::date',
"truncate" => 'Aspect\Modifier::truncate',
"escape" => 'Aspect\Modifier::escape',
"e" => 'Aspect\Modifier::escape', // alias of escape
"url" => 'urlencode', // alias of escape:"url"
"unescape" => 'Aspect\Modifier::unescape',
"strip_tags" => 'strip_tags',
"strip" => 'Aspect\Modifier::strip',
"default" => 'Aspect\Modifier::defaultValue',
"isset" => 'isset',
"empty" => 'empty'
"default" => 'Aspect\Modifier::defaultValue'
);
/**
* @var array list of allowed PHP functions
* @var array of allowed PHP functions
*/
protected $_allowed_funcs = array(
"empty" => 1, "isset" => 1, "count" => 1, "is_string" => 1, "is_array" => 1, "is_numeric" => 1, "is_int" => 1, "is_object" => 1
"empty" => 1, "isset" => 1, "count" => 1, "is_string" => 1, "is_array" => 1, "is_numeric" => 1, "is_int" => 1,
"is_object" => 1, "strtotime" => 1, "gettype" => 1, "is_double" => 1, "json_encode" => 1, "json_decode" => 1,
"ip2long" => 1, "long2ip" => 1, "strip_tags" => 1, "nl2br" => 1
);
/**
* @var array list of compilers and functions
* @var array of compilers and functions
*/
protected $_actions = array(
'foreach' => array(
'foreach' => array( // {foreach ...} {break} {continue} {foreachelse} {/foreach}
'type' => self::BLOCK_COMPILER,
'open' => 'Aspect\Compiler::foreachOpen',
'close' => 'Aspect\Compiler::foreachClose',
@ -134,7 +143,7 @@ class Aspect {
),
'float_tags' => array('break' => 1, 'continue' => 1)
),
'if' => array(
'if' => array( // {if ...} {elseif ...} {else} {/if}
'type' => self::BLOCK_COMPILER,
'open' => 'Aspect\Compiler::ifOpen',
'close' => 'Aspect\Compiler::stdClose',
@ -143,7 +152,7 @@ class Aspect {
'else' => 'Aspect\Compiler::tagElse',
)
),
'switch' => array(
'switch' => array( // {switch ...} {case ...} {break} {default} {/switch}
'type' => self::BLOCK_COMPILER,
'open' => 'Aspect\Compiler::switchOpen',
'close' => 'Aspect\Compiler::stdClose',
@ -154,7 +163,7 @@ class Aspect {
),
'float_tags' => array('break' => 1)
),
'for' => array(
'for' => array( // {for ...} {break} {continue} {/for}
'type' => self::BLOCK_COMPILER,
'open' => 'Aspect\Compiler::forOpen',
'close' => 'Aspect\Compiler::forClose',
@ -165,7 +174,7 @@ class Aspect {
),
'float_tags' => array('break' => 1, 'continue' => 1)
),
'while' => array(
'while' => array( // {while ...} {break} {continue} {/while}
'type' => self::BLOCK_COMPILER,
'open' => 'Aspect\Compiler::whileOpen',
'close' => 'Aspect\Compiler::stdClose',
@ -175,24 +184,24 @@ class Aspect {
),
'float_tags' => array('break' => 1, 'continue' => 1)
),
'include' => array(
'include' => array( // {include ...}
'type' => self::INLINE_COMPILER,
'parser' => 'Aspect\Compiler::tagInclude'
),
'var' => array(
'var' => array( // {var ...}
'type' => self::INLINE_COMPILER,
'parser' => 'Aspect\Compiler::assign'
),
'block' => array(
'block' => array( // {block ...} {/block}
'type' => self::BLOCK_COMPILER,
'open' => 'Aspect\Compiler::tagBlockOpen',
'close' => 'Aspect\Compiler::tagBlockClose',
),
'extends' => array(
'extends' => array( // {extends ...}
'type' => self::INLINE_COMPILER,
'parser' => 'Aspect\Compiler::tagExtends'
),
'capture' => array(
'capture' => array( // {capture ...} {/capture}
'type' => self::BLOCK_FUNCTION,
'open' => 'Aspect\Compiler::stdFuncOpen',
'close' => 'Aspect\Compiler::stdFuncClose',
@ -206,8 +215,16 @@ class Aspect {
);
public static function factory($template_dir, $compile_dir, $options = 0) {
$aspect = new static();
/**
* Factory
* @param string $template_dir path to templates
* @param string $compile_dir path to compiled files
* @param int $options
* @param \Aspect\Provider $provider
* @return Aspect
*/
public static function factory($template_dir, $compile_dir, $options = 0, Aspect\Provider $provider = null) {
$aspect = new static($provider);
$aspect->setCompileDir($compile_dir);
$aspect->setTemplateDirs($template_dir);
if($options) {
@ -216,34 +233,76 @@ class Aspect {
return $aspect;
}
/**
* @param Aspect\Provider $provider
*/
public function __construct(Aspect\Provider $provider = null) {
$this->_provider = $provider ?: new Aspect\Provider();
}
/**
* Set checks template for modifications
* @param $state
* @return Aspect
*/
public function setCompileCheck($state) {
$state && ($this->_options |= self::CHECK_MTIME);
return $this;
}
/**
* Set force template compiling
* @param $state
* @return Aspect
*/
public function setForceCompile($state) {
$state && ($this->_options |= self::FORCE_COMPILE);
$this->_storage = $state ? new Aspect\BlackHole() : array();
return $this;
}
/**
* Set compile directory
* @param string $dir directory to store compiled templates in
* @return Aspect
*/
public function setCompileDir($dir) {
$this->_compile_dir = $dir;
return $this;
}
/**
* Set template directory
* @param string|array $dirs directory(s) of template sources
* @return Aspect
*/
public function setTemplateDirs($dirs) {
$this->_tpl_path = (array)$dirs;
$this->_provider->setTemplateDirs($dirs);
return $this;
}
/*public function addPostCompileFilter($cb) {
$this->_post_cmp[] = $cb;
/**
*
* @param callable $cb
*/
public function addPreCompileFilter($cb) {
$this->_on_pre_cmp[] = $cb;
}
/**
*
* @param callable $cb
*/
public function addPostCompileFilter($cb) {
$this->_on_post_cmp[] = $cb;
}
/**
* @param callable $cb
*/
public function addCompileFilter($cb) {
$this->_cmp[] = $cb;
}*/
$this->_on_cmp[] = $cb;
}
/**
* Add modifier
@ -252,17 +311,17 @@ class Aspect {
* @param string $callback
* @return Aspect
*/
public function setModifier($modifier, $callback) {
public function addModifier($modifier, $callback) {
$this->_modifiers[$modifier] = $callback;
return $this;
}
/**
* @param $compiler
* @param $parser
* @param string $compiler
* @param string $parser
* @return Aspect
*/
public function setCompiler($compiler, $parser) {
public function addCompiler($compiler, $parser) {
$this->_actions[$compiler] = array(
'type' => self::INLINE_COMPILER,
'parser' => $parser
@ -271,31 +330,46 @@ class Aspect {
}
/**
* @param $compiler
* @param array $parsers
* @param string $compiler
* @param string $open_parser
* @param string $close_parser
* @param array $tags
* @return Aspect
*/
public function setBlockCompiler($compiler, array $parsers, array $tags = array()) {
public function addBlockCompiler($compiler, $open_parser, $close_parser = self::DEFAULT_CLOSE_COMPILER, array $tags = array()) {
$this->_actions[$compiler] = array(
'type' => self::BLOCK_COMPILER,
'open' => $parsers["open"],
'close' => isset($parsers["close"]) ? $parsers["close"] : 'Aspect\Compiler::stdClose',
'open' => $open_parser,
'close' => $close_parser ?: self::DEFAULT_CLOSE_COMPILER,
'tags' => $tags,
);
return $this;
}
/**
* @param $function
* @param $callback
* @param null $parser
* @param string $function
* @param callable $callback
* @param string $parser
* @return Aspect
*/
public function setFunction($function, $callback, $parser = null) {
public function addFunction($function, $callback, $parser = self::DEFAULT_FUNC_PARSER) {
$this->_actions[$function] = array(
'type' => self::INLINE_FUNCTION,
'parser' => $parser ?: 'Aspect\Compiler::stdFuncParser',
'parser' => $parser ?: self::DEFAULT_FUNC_PARSER,
'function' => $callback,
);
return $this;
}
/**
* @param string $function
* @param callable $callback
* @return Aspect
*/
public function addFunctionSmart($function, $callback) {
$this->_actions[$function] = array(
'type' => self::INLINE_FUNCTION,
'parser' => self::SMART_FUNC_PARSER,
'function' => $callback,
);
return $this;
@ -308,7 +382,7 @@ class Aspect {
* @param null $parser_close
* @return Aspect
*/
public function setBlockFunction($function, $callback, $parser_open = null, $parser_close = null) {
public function addBlockFunction($function, $callback, $parser_open = null, $parser_close = null) {
$this->_actions[$function] = array(
'type' => self::BLOCK_FUNCTION,
'open' => $parser_open ?: 'Aspect\Compiler::stdFuncOpen',
@ -322,29 +396,11 @@ class Aspect {
* @param array $funcs
* @return Aspect
*/
public function setAllowedFunctions(array $funcs) {
public function addAllowedFunctions(array $funcs) {
$this->_allowed_funcs = $this->_allowed_funcs + array_flip($funcs);
return $this;
}
/**
* @param callable $callback
* @return Aspect
*/
public function setFunctionsLoader($callback) {
$this->_loader_func = $callback;
return $this;
}
/**
* @param callable $callback
* @return Aspect
*/
public function setModifiersLoader($callback) {
$this->_loader_mod = $callback;
return $this;
}
/**
* @param $modifier
* @return mixed
@ -355,8 +411,6 @@ class Aspect {
return $this->_modifiers[$modifier];
} elseif($this->isAllowedFunction($modifier)) {
return $modifier;
} elseif($this->_loader_mod && $this->_loadModifier($modifier)) {
return $this->_modifiers[$modifier];
} else {
throw new \Exception("Modifier $modifier not found");
}
@ -369,29 +423,6 @@ class Aspect {
public function getFunction($function) {
if(isset($this->_actions[$function])) {
return $this->_actions[$function];
} elseif($this->_loader_func && $this->_loadFunction($function)) {
return $this->_actions[$function];
} else {
return false;
}
}
private function _loadModifier($modifier) {
$mod = call_user_func($this->_loader_mod, $modifier);
if($mod) {
$this->_modifiers[$modifier] = $mod;
return true;
} else {
return false;
}
}
private function _loadFunction($function) {
$func = call_user_func($this->_loader_func, $function);
if($func && isset(self::$_actions_defaults[ $func["type"] ])) {
$this->_actions[$function] = $func + self::$_actions_defaults[ $func["type"] ];
return true;
} else {
return false;
}
@ -419,23 +450,23 @@ class Aspect {
* Add template directory
* @static
* @param string $dir
* @return \Aspect
* @throws \InvalidArgumentException
*/
public function addTemplateDir($dir) {
$_dir = realpath($dir);
if(!$_dir) {
throw new \InvalidArgumentException("Invalid template dir: $dir");
$this->_provider->addTemplateDir($dir);
return $this;
}
$this->_tpl_path[] = $_dir;
public function addProvider($scm, \Aspect\Provider $provider) {
$this->_providers[$scm] = $provider;
}
/**
* Set options. May be bitwise mask of constants DENY_METHODS, DENY_INLINE_FUNCS, DENY_SET_VARS, INCLUDE_SOURCES,
* FORCE_COMPILE, CHECK_MTIME, or associative array with boolean values:
* disable_methods - disable all call method in template
* disable_methods - disable all calls method in template
* disable_native_funcs - disable all native PHP functions in template
* disable_set_vars - forbidden rewrite variables
* include_sources - insert comments with source code into compiled template
* force_compile - recompile template every time (very slow!)
* compile_check - check template modifications (slow!)
* @param int|array $options
@ -456,6 +487,23 @@ class Aspect {
return $this->_options;
}
/**
* @param bool|string $scm
* @return Aspect\Provider
* @throws InvalidArgumentException
*/
public function getProvider($scm = false) {
if($scm) {
if(isset($this->_provider[$scm])) {
return $this->_provider[$scm];
} else {
throw new InvalidArgumentException("Provider for '$scm' not found");
}
} else {
return $this->_provider;
}
}
/**
* Execute template and write result into stdout
*
@ -486,8 +534,11 @@ class Aspect {
* @return Aspect\Template
*/
public function getTemplate($template) {
if(isset($this->_storage[ $template ])) {
if(($this->_options & self::CHECK_MTIME) && !$this->_check($template)) {
/** @var Aspect\Template $tpl */
$tpl = $this->_storage[ $template ];
if(($this->_options & self::CHECK_MTIME) && !$tpl->isValid()) {
return $this->_storage[ $template ] = $this->compile($template);
} else {
return $this->_storage[ $template ];
@ -503,7 +554,7 @@ class Aspect {
* Add custom template into storage
* @param Aspect\Render $template
*/
public function storeTemplate(Aspect\Render $template) {
public function addTemplate(Aspect\Render $template) {
$this->_storage[ $template->getName() ] = $template;
$template->setStorage($this);
}
@ -517,7 +568,7 @@ class Aspect {
*/
protected function _load($tpl) {
$file_name = $this->_getHash($tpl);
if(!is_file($this->_compile_dir."/".$file_name) || ($this->_options & self::CHECK_MTIME) && !$this->_check($tpl)) {
if(!is_file($this->_compile_dir."/".$file_name)) {
return $this->compile($tpl);
} else {
/** @var Aspect\Render $tpl */
@ -527,25 +578,6 @@ class Aspect {
}
}
/**
* @param string $template
* @return bool
*/
private function _check($template) {
return $this->_isActual($template, filemtime($this->_compile_dir."/".$this->_getHash($template)));
}
/**
* Check, if template is actual
* @param $template
* @param $compiled_time
* @return bool
*/
protected function _isActual($template, $compiled_time) {
clearstatcache(false, $template = $this->_getTemplatePath($template));
return filemtime($template) < $compiled_time;
}
/**
* Generate unique name of compiled template
*
@ -560,14 +592,15 @@ class Aspect {
/**
* Compile and save template
*
*
* @param string $tpl
* @throws \RuntimeException
* @param bool $store
* @throws RuntimeException
* @return \Aspect\Template
*/
public function compile($tpl) {
$file_name = $this->_compile_dir."/".$this->_getHash($tpl);
$template = new Template($this, $this->_loadCode($tpl), $tpl);
public function compile($tpl, $store = true) {
$provider = $this->getProvider(strstr($tpl, ":", true));
$template = new Template($this, $provider->loadCode($tpl), $tpl);
if($store) {
$tpl_tmp = tempnam($this->_compile_dir, basename($tpl));
$tpl_fp = fopen($tpl_tmp, "w");
if(!$tpl_fp) {
@ -575,27 +608,29 @@ class Aspect {
}
fwrite($tpl_fp, $template->getTemplateCode());
fclose($tpl_fp);
$file_name = $this->_compile_dir."/".$this->_getHash($tpl);
if(!rename($tpl_tmp, $file_name)) {
throw new \RuntimeException("Can not to move $tpl_tmp to $tpl");
}
}
return $template;
}
/**
* Remove all compiled templates. Warning! Do cleanup the compiled directory.
* Remove all compiled templates.
*
* @param string $scm
* @return int
* @api
*/
public function compileAll() {
public function compileAll($scm = null) {
//return FS::rm($this->_compile_dir.'/*');
}
/**
* @param string $tpl
* @return bool
* @api
*/
public function clearCompileTemplate($tpl) {
public function clearCompiledTemplate($tpl) {
$file_name = $this->_compile_dir."/".$this->_getHash($tpl);
if(file_exists($file_name)) {
return unlink($file_name);
@ -606,38 +641,11 @@ class Aspect {
/**
* @return int
* @api
*/
public function clearAllCompiles() {
}
/**
* Get template path
* @param $tpl
* @return string
* @throws \RuntimeException
*/
private function _getTemplatePath($tpl) {
foreach($this->_tpl_path as $tpl_path) {
if(($path = stream_resolve_include_path($tpl_path."/".$tpl)) && strpos($path, $tpl_path) === 0) {
return $path;
}
}
throw new \RuntimeException("Template $tpl not found");
}
/**
* Code loader
*
* @param string $tpl
* @return string
* @throws \RuntimeException
*/
protected function _loadCode(&$tpl) {
return file_get_contents($tpl = $this->_getTemplatePath($tpl));
}
/**
* Compile code to template
*

View File

@ -4,6 +4,9 @@ use Aspect\Tokenizer;
use Aspect\Template;
use Aspect\Scope;
/**
* Compilers collection
*/
class Compiler {
/**
* Tag {include ...}
@ -11,7 +14,7 @@ class Compiler {
* @static
* @param Tokenizer $tokens
* @param Template $tpl
* @throws \Exception
* @throws ImproperUseException
* @return string
*/
public static function tagInclude(Tokenizer $tokens, Template $tpl) {
@ -22,7 +25,7 @@ class Compiler {
} elseif (isset($p["file"])) {
$file_name = $p["file"];
} else {
throw new \Exception("{include} require 'file' parameter");
throw new ImproperUseException("The tag {include} requires 'file' parameter");
}
unset($p["file"], $p[0]);
if($p) {
@ -51,15 +54,13 @@ class Compiler {
*
* @static
* @param Tokenizer $tokens
* @param Tokenizer $tokens
* @param Scope $scope
* @throws \Exception
* @internal param \Exception $
* @throws ImproperUseException
* @return string
*/
public static function tagElseIf(Tokenizer $tokens, Scope $scope) {
if($scope["else"]) {
throw new \Exception('Incorrect use of the tag {else if}');
throw new ImproperUseException('Incorrect use of the tag {elseif}');
}
return '} elseif('.$scope->tpl->parseExp($tokens, true).') {';
}
@ -85,8 +86,7 @@ class Compiler {
* @param Tokenizer $tokens
* @param Tokenizer $tokens
* @param Scope $scope
* @throws \Exception
* @internal param \Exception $
* @throws ImproperUseException
* @return string
*/
public static function foreachOpen(Tokenizer $tokens, Scope $scope) {
@ -102,12 +102,7 @@ class Compiler {
$prepend = $uid.' = '.$from.';';
$from = $uid;
} else {
if($tokens->valid()) {
throw new \Exception("Unexpected token '".$tokens->current()."' in 'foreach'");
} else {
throw new \Exception("Unexpected end of 'foreach'");
}
throw new UnexpectedException($tokens, null, "tag {foreach}");
}
$tokens->get(T_AS);
$tokens->next();
@ -124,7 +119,7 @@ class Compiler {
while($token = $tokens->key()) {
$param = $tokens->get(T_STRING);
if(!isset($p[ $param ])) {
throw new \Exception("Unknown parameter '$param'");
throw new ImproperUseException("Unknown parameter '$param' in {foreach}");
}
$tokens->getNext("=");
$tokens->next();
@ -166,7 +161,7 @@ class Compiler {
* @param Scope $scope
* @return string
*/
public static function foreachElse(Tokenizer $tokens, Scope $scope) {
public static function foreachElse($tokens, Scope $scope) {
$scope["no-break"] = $scope["no-continue"] = $scope["else"] = true;
return " {$scope['after']} } } else {";
}
@ -179,7 +174,7 @@ class Compiler {
* @param Scope $scope
* @return string
*/
public static function foreachClose(Tokenizer $tokens, Scope $scope) {
public static function foreachClose($tokens, Scope $scope) {
if($scope["else"]) {
return '}';
} else {
@ -192,7 +187,7 @@ class Compiler {
* @param Tokenizer $tokens
* @param Scope $scope
* @return string
* @throws \Exception
* @throws ImproperUseException
*/
public static function forOpen(Tokenizer $tokens, Scope $scope) {
$p = array("index" => false, "first" => false, "last" => false, "step" => 1, "to" => false, "max" => false, "min" => false);
@ -213,7 +208,7 @@ class Compiler {
$condition = "$var >= {$p['to']}";
if($p["last"]) $c = "($var + {$p['step']}) < {$p['to']}";
} else {
throw new \Exception("Invalid step value");
throw new ImproperUseException("Invalid step value if {for}");
}
} else {
$condition = "({$p['step']} > 0 && $var <= {$p['to']} || {$p['step']} < 0 && $var >= {$p['to']})";
@ -261,7 +256,7 @@ class Compiler {
* @param Scope $scope
* @return string
*/
public static function forClose(Tokenizer $tokens, Scope $scope) {
public static function forClose($tokens, Scope $scope) {
if($scope["else"]) {
return '}';
} else {
@ -318,14 +313,14 @@ class Compiler {
* @static
* @param Tokenizer $tokens
* @param Scope $scope
* @throws \Exception
* @throws ImproperUseException
* @return string
*/
public static function tagContinue(Tokenizer $tokens, Scope $scope) {
public static function tagContinue($tokens, Scope $scope) {
if(empty($scope["no-continue"])) {
return 'continue;';
} else {
throw new \Exception("Incorrect use of the tag {continue}");
throw new ImproperUseException("Improper usage of the tag {continue}");
}
}
@ -335,7 +330,7 @@ class Compiler {
* @static
* @return string
*/
public static function tagDefault(Tokenizer $tokens, Scope $scope) {
public static function tagDefault($tokens, Scope $scope) {
$code = 'default: ';
if($scope["switch"]) {
unset($scope["no-break"], $scope["no-continue"]);
@ -351,20 +346,37 @@ class Compiler {
* @static
* @param Tokenizer $tokens
* @param Scope $scope
* @throws \Exception
* @throws ImproperUseException
* @return string
*/
public static function tagBreak(Tokenizer $tokens, Scope $scope) {
public static function tagBreak($tokens, Scope $scope) {
if(empty($scope["no-break"])) {
return 'break;';
} else {
throw new \Exception("Incorrect use of the tag {break}");
throw new ImproperUseException("Improper usage of the tag {break}");
}
}
/**
* check if value is scalar, like "string", 2, 2.2, true, false, null
* @param string $value
* @return bool
* @todo add 'string' support
*/
public static function isScalar($value) {
return json_decode($value);
}
/**
* Dispatch {extends} tag
* @param Tokenizer $tokens
* @param Template $tpl
* @throws ImproperUseException
* @return string
*/
public static function tagExtends(Tokenizer $tokens, Template $tpl) {
if(!empty($tpl->_extends)) {
throw new \Exception("Only one {extends} allowed");
throw new ImproperUseException("Only one {extends} allowed");
}
$p = $tpl->parseParams($tokens);
if(isset($p[0])) {
@ -372,17 +384,63 @@ class Compiler {
} elseif (isset($p["file"])) {
$tpl_name = $p["file"];
} else {
throw new \Exception("{extends} require 'file' parameter");
throw new ImproperUseException("{extends} require 'file' parameter");
}
$tpl->addPostCompile(__CLASS__."::extendBody");
if($name = self::isScalar($tpl_name)) { // static extends
$tpl->_extends = $tpl->getStorage()->compile($name, false);
$tpl->addDepend($tpl->getStorage()->getTemplate($name)); // for valid compile-time need take template from storage
return "/* Static extends */";
} else { // dynamic extends
$tpl->_extends = $tpl_name;
return '$parent = $tpl->getStorage()->getTemplate('.$tpl_name.');';
return '/* Dynamic extends */'."\n".'$parent = $tpl->getStorage()->getTemplate('.$tpl_name.');';
}
}
public static function extendBody(&$body, Template $tpl) {
$body = '<?php if(!isset($tpl->blocks)) {$tpl->blocks = array();} ob_start(); ?>'.$body.'<?php ob_end_clean(); $parent->blocks = &$tpl->blocks; $parent->display((array)$tpl); unset($tpl->blocks, $parent->blocks); ?>';
/**
* Post compile method for {extends ...} tag
* @param $body
* @param Template $tpl
*/
public static function extendBody(&$body, $tpl) {
if(isset($tpl->_extends)) { // is child
if(is_object($tpl->_extends)) { // static extends
$t = $tpl;
while(isset($t->_extends)) {
$t->compile();
$t->_blocks += (array)$t->_extends->_blocks;
$t = $t->_extends;
}
if(empty($t->_blocks)) {
$body = $t->getBody();
} else {
$b = $t->getBody();
foreach($t->_blocks as $name => $pos) {
}
}
} else { // dynamic extends
$body .= '<?php $parent->blocks = &$tpl->blocks; $parent->display((array)$tpl); unset($tpl->blocks, $parent->blocks); ?>';
//return '$tpl->blocks['.$scope["name"].'] = ob_get_clean();';
}
}
/*$body = '<?php if(!isset($tpl->blocks)) {$tpl->blocks = array();} ob_start(); ?>'.$body.'<?php ob_end_clean(); $parent->blocks = &$tpl->blocks; $parent->display((array)$tpl); unset($tpl->blocks, $parent->blocks); ?>';*/
}
public static function tagUse(Tokenizer $tokens, Template $tpl) {
}
/**
* Tag {block ...}
* @param Tokenizer $tokens
* @param Scope $scope
* @return string
* @throws ImproperUseException
*/
public static function tagBlockOpen(Tokenizer $tokens, Scope $scope) {
$p = $scope->tpl->parseParams($tokens);
if(isset($p["name"])) {
@ -390,23 +448,63 @@ class Compiler {
} elseif (isset($p[0])) {
$scope["name"] = $p[0];
} else {
throw new \Exception("{block} require name parameter");
throw new ImproperUseException("{block} must be named");
}
if($scope->closed) {
return 'isset($tpl->blocks['.$scope["name"].']) ? $tpl->blocks[] : "" ;';
if(isset($scope->tpl->_extends)) { // is child
if(is_object($scope->tpl->_extends)) { // static extends
$code = "";
} else { // dynamic extends
$code = 'if(empty($tpl->blocks['.$scope["name"].'])) { ob_start();';
}
} else { // is parent
if(isset($scope->tpl->_blocks[ $scope["name"] ])) { // skip own block and insert child's block after
$scope["body"] = $scope->tpl->_body;
$scope->tpl->_body = "";
return '';
} else {
return 'ob_start();';
$code = 'if(isset($tpl->blocks['.$scope["name"].'])) { echo $tpl->blocks['.$scope["name"].']; } else {';
}
}
$scope["offset"] = strlen($scope->tpl->getBody()) + strlen($code);
return $code;
}
public static function tagBlockClose(Tokenizer $tokens, Scope $scope) {
if(isset($scope->tpl->_extends)) {
/**
* Close tag {/block}
* @param Tokenizer $tokens
* @param Scope $scope
* @return string
*/
public static function tagBlockClose($tokens, Scope $scope) {
$scope->tpl->_blocks[ self::isScalar($scope["name"]) ] = substr($scope->tpl->getBody(), $scope["offset"]);
if(isset($scope->tpl->_extends)) { // is child
if(is_object($scope->tpl->_extends)) { // static extends
return "";
} else { // dynamic extends
return '$tpl->blocks['.$scope["name"].'] = ob_get_clean(); }';
}
} else { // is parent
if(isset($scope["body"])) {
$scope->tpl->_body = $scope["body"].$scope->tpl->_blocks[ $scope["name"] ];
return "";
} else {
return '}';
}
}
/* $scope->tpl->_blocks[ $scope["name"] ] = substr($scope->tpl->getBody(), $scope["offset"]);
return '}';*/
/*if(isset($scope->tpl->_extends) && is_object($scope->tpl->_extends)) {
//var_dump("fetched block ".$scope->tpl->_blocks[ $scope["name"] ]);
} else {
return '}';
}*/
/*if(isset($scope->tpl->_extends)) {
$var = '$i'.$scope->tpl->i++;
return $var.' = ob_get_clean(); if('.$var.') $tpl->blocks['.$scope["name"].'] = '.$var.';';
} else {
return 'if(empty($tpl->blocks['.$scope["name"].'])) { ob_end_flush(); } else { print($tpl->blocks['.$scope["name"].']); ob_end_clean(); }';
}
}*/
}
/**
@ -420,7 +518,7 @@ class Compiler {
}
/**
* Standard function tag parser
* Standard function parser
*
* @static
* @param $function
@ -432,6 +530,35 @@ class Compiler {
return "echo $function(".self::_toArray($tpl->parseParams($tokens)).', $tpl);';
}
/**
* Smart function parser
*
* @static
* @param $function
* @param Tokenizer $tokens
* @param Template $tpl
* @return string
*/
public static function smartFuncParser($function, Tokenizer $tokens, Template $tpl) {
if(strpos($function, "::")) {
$ref = new \ReflectionMethod($function);
} else {
$ref = new \ReflectionFunction($function);
}
$args = array();
$params = $tpl->parseParams($tokens);
foreach($ref->getParameters() as $param) {
if(isset($params[ $param->getName() ])) {
$args[] = $params[ $param->getName() ];
} elseif(isset($params[ $param->getPosition() ])) {
$args[] = $params[ $param->getPosition() ];
} elseif($param->isOptional()) {
$args[] = $param->getDefaultValue();
}
}
return "echo $function(".implode(", ", $args).');';
}
/**
* Standard function open tag parser
*
@ -453,7 +580,7 @@ class Compiler {
* @param Scope $scope
* @return string
*/
public static function stdFuncClose(Tokenizer $tokens, Scope $scope) {
public static function stdFuncClose($tokens, Scope $scope) {
return "echo ".$scope["function"].'('.$scope["params"].', ob_get_clean(), $tpl);';
}
@ -478,6 +605,13 @@ class Compiler {
return self::setVar($tokens, $tpl).';';
}
/**
* Set variable expression parser
* @param Tokenizer $tokens
* @param Template $tpl
* @param bool $allow_array
* @return string
*/
public static function setVar(Tokenizer $tokens, Template $tpl, $allow_array = true) {
$var = $tpl->parseVar($tokens, $tpl::DENY_MODS);
@ -490,4 +624,13 @@ class Compiler {
}
}
public static function tagModifyOpen(Tokenizer $tokens, Scope $scope) {
$scope["modifiers"] = $scope->tpl->parseModifier($tokens, "ob_get_clean()");
return "ob_start();";
}
public static function tagModifyClose($tokens, Scope $scope) {
return "echo ".$scope["modifiers"].";";
}
}

View File

@ -1,7 +1,7 @@
<?php
namespace Aspect;
class Misc {
/**

78
src/Aspect/Provider.php Normal file
View File

@ -0,0 +1,78 @@
<?php
namespace Aspect;
/**
* Templates provider
* @author Ivan Shalganov
*/
class Provider implements ProviderInterface {
private $_tpl_path = array();
public function setTemplateDirs($dirs) {
foreach((array)$dirs as $dir) {
$this->addTemplateDir($dir);
}
return $this;
}
public function addTemplateDir($dir) {
if($_dir = realpath($dir)) {
$this->_tpl_path[] = $_dir;
} else {
throw new \LogicException("Template directory {$dir} doesn't exists");
}
}
/**
* @param string $tpl
* @return string
*/
public function loadCode($tpl) {
return file_get_contents($tpl = $this->_getTemplatePath($tpl));
}
public function getLastModified($tpl) {
clearstatcache(null, $tpl = $this->_getTemplatePath($tpl));
return filemtime($tpl);
}
public function getAll() {
}
/**
* Get template path
* @param $tpl
* @return string
* @throws \RuntimeException
*/
private function _getTemplatePath($tpl) {
foreach($this->_tpl_path as $tpl_path) {
if(($path = realpath($tpl_path."/".$tpl)) && strpos($path, $tpl_path) === 0) {
return $path;
}
}
throw new \RuntimeException("Template $tpl not found");
}
/**
* @param string $tpl
* @return bool
*/
public function isTemplateExists($tpl) {
foreach($this->_tpl_path as $tpl_path) {
if(($path = realpath($tpl_path."/".$tpl)) && strpos($path, $tpl_path) === 0) {
return true;
}
}
return false;
}
public function getLastModifiedBatch($tpls) {
$tpls = array_flip($tpls);
foreach($tpls as $tpl => &$time) {
$time = $this->getLastModified($tpl);
}
return $tpls;
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Aspect;
interface ProviderInterface {
/**
* @param string $tpl
* @return bool
*/
public function isTemplateExists($tpl);
/**
* @param string $tpl
* @return string
*/
public function loadCode($tpl);
/**
* @param string $tpl
* @return int
*/
public function getLastModified($tpl);
public function getLastModifiedBatch($tpls);
/**
* @return array
*/
public function getAll();
}

View File

@ -20,20 +20,23 @@ class Render extends \ArrayObject {
*/
protected $_aspect;
/**
* Signature of the template
* @var mixed
* Timestamp of compilation
* @var float
*/
protected $_fingerprint;
protected $_time = 0.0;
protected $_depends = array();
/**
* @param string $name template name
* @param callable $code template body
* @param mixed $fingerprint signature
* @param mixed $props signature
*/
public function __construct($name, \Closure $code, $fingerprint = null) {
public function __construct($name, \Closure $code, $props = array()) {
$this->_name = $name;
$this->_code = $code;
$this->_fingerprint = $fingerprint;
$this->_time = isset($props["time"]) ? $props["time"] : microtime(true);
$this->_depends = isset($props["depends"]) ? $props["depends"] : array();
}
/**
@ -67,17 +70,26 @@ class Render extends \ArrayObject {
return $this->_name;
}
public function getCompileTime() {
return $this->_time;
}
/**
* Validate template version
* @param mixed $fingerprint of the template
* Validate template
* @return bool
*/
public function isValid($fingerprint) {
if($this->_fingerprint) {
return $fingerprint === $this->_fingerprint;
} else {
return true;
public function isValid() {
$provider = $this->_aspect->getProvider(strstr($this->_name, ":"), true);
if($provider->getLastModified($this->_name) >= $this->_time) {
return false;
}
foreach($this->_depends as $tpl => $time) {
if($this->_aspect->getTemplate($tpl)->getCompileTime() !== $time) {
return false;
}
}
return true;
}
/**

View File

@ -19,7 +19,7 @@ class Template extends Render {
* Template PHP code
* @var string
*/
private $_body;
public $_body;
/**
* Call stack
* @var Scope[]
@ -41,7 +41,7 @@ class Template extends Render {
/**
* @var bool
*/
private $_literal = false;
private $_ignore = false;
/**
* Options
* @var int
@ -57,45 +57,57 @@ class Template extends Render {
* @param Aspect $aspect Template storage
* @param string $code template source
* @param string $name template name
* @throws CompileException
* @param bool $auto_compile
*/
public function __construct(Aspect $aspect, $code, $name = "runtime template") {
public function __construct(Aspect $aspect, $code, $name = "runtime template", $auto_compile = true) {
$this->_src = $code;
$this->_name = $name;
$this->_aspect = $aspect;
$this->_options = $aspect->getOptions();
if($auto_compile) {
$this->compile();
}
}
public function compile() {
if(!isset($this->_src)) {
return;
}
$this->_time = microtime(true);
$pos = 0;
while(($start = strpos($code, '{', $pos)) !== false) { // search open-char of tags
switch($code[$start + 1]) { // check next char
while(($start = strpos($this->_src, '{', $pos)) !== false) { // search open-char of tags
switch($this->_src[$start + 1]) { // check next char
case "\n": case "\r": case "\t": case " ": case "}": // ignore the tag
$pos = $start + 1; // trying finding tags after the current char
$pos = $start + 1; // try find tags after the current char
continue 2;
case "*": // if comment block
$end = strpos($code, '*}', $start); // finding end of the comment block
$frag = substr($code, $this->_pos, $start - $end); // read the comment block for precessing
$end = strpos($this->_src, '*}', $start); // finding end of the comment block
$frag = substr($this->_src, $this->_pos, $start - $end); // read the comment block for precessing
$this->_line += substr_count($frag, "\n"); // count skipped lines
$pos = $end + 1; // trying finding tags after the comment block
continue 2;
}
$end = strpos($code, '}', $start); // search close-char of the tag
$end = strpos($this->_src, '}', $start); // search close-char of the tag
if(!$end) { // if unexpected end of template
throw new CompileException("Unclosed tag in line {$this->_line}", 0, 1, $this->_name, $this->_line);
}
$frag = substr($code, $this->_pos, $start - $this->_pos); // variable $frag contains chars after last '}' and new '{'
$tag = substr($code, $start, $end - $start + 1); // variable $tag contains aspect tag '{...}'
$this->_line += substr_count($code, "\n", $this->_pos, $end - $start + 1); // count lines in $frag and $tag (using original text $code)
$pos = $this->_pos = $end + 1; // move search pointer to end of the tag
$frag = substr($this->_src, $this->_pos, $start - $this->_pos); // variable $frag contains chars after last '}' and next '{'
$tag = substr($this->_src, $start, $end - $start + 1); // variable $tag contains aspect tag '{...}'
$this->_line += substr_count($this->_src, "\n", $this->_pos, $end - $start + 1); // count lines in $frag and $tag (using original text $code)
$pos = $this->_pos = $end + 1; // move search-pointer to end of the tag
if($this->_trim) { // if previous tag has trim flag
$frag = ltrim($frag);
}
$tag = $this->_tag($tag, $this->_trim);
$tag = $this->_tag($tag, $this->_trim); // dispatching tags
if($this->_trim) { // if current tag has trim flag
$frag = rtrim($frag);
}
$this->_body .= $frag.$tag;
$this->_body .= str_replace("<?", '<?php echo "<?" ?>', $frag).$tag;
}
$this->_body .= substr($code, $this->_pos);
$this->_body .= substr($this->_src, $this->_pos);
if($this->_stack) {
$_names = array();
$_line = 0;
@ -105,7 +117,7 @@ class Template extends Render {
}
$_names[] = $scope->name.' defined on line '.$scope->line;
}
throw new CompileException("Unclosed tags: ".implode(", ", $_names), 0, 1, $this->_name, $_line);
throw new CompileException("Unclosed block tags: ".implode(", ", $_names), 0, 1, $this->_name, $_line);
}
unset($this->_src);
if($this->_post) {
@ -128,13 +140,18 @@ class Template extends Render {
}
/**
* Return PHP code of PHP file of template
* Return PHP code for saving to file
* @return string
*/
public function getTemplateCode() {
return "<?php \n".
"/** Aspect template '".$this->_name."' compiled at ".date('Y-m-d H:i:s')." */\n".
"return new Aspect\\Render('{$this->_name}', ".$this->_getClosureCode().", ".$this->_options.");\n";
"return new Aspect\\Render('{$this->_name}', ".$this->_getClosureCode().", ".var_export(array(
"options" => $this->_options,
//"provider" =>
"time" => $this->_time,
"depends" => $this->_depends
), true).");\n";
}
/**
@ -164,6 +181,14 @@ class Template extends Render {
}
/**
* Add depends from template
* @param Render $tpl
*/
public function addDepend(Render $tpl) {
$this->_depends[$tpl->getName()] = $tpl->getCompileTime();
}
/**
* Execute template and return result as string
* @param array $values for template
@ -198,9 +223,9 @@ class Template extends Render {
$trim = false;
}
$token = trim($token);
if($this->_literal) {
if($token === '/literal') {
$this->_literal = false;
if($this->_ignore) {
if($token === '/ignore') {
$this->_ignore = false;
return '';
} else {
return $src;
@ -229,15 +254,17 @@ class Template extends Render {
if($tokens->key()) { // if tokenizer still have tokens
throw new UnexpectedException($tokens);
}
if($this->_options & Aspect::INCLUDE_SOURCES) {
return "<?php\n/* {$this->_name}:{$this->_line}: {$src} */\n {$code} ?>";
if(!$code) {
return "";
} else {
return "<?php {$code} ?>";
return "<?php\n/* {$this->_name}:{$this->_line}: {$src} */\n {$code} ?>";
}
} catch (ImproperUseException $e) {
throw new CompileException($e->getMessage()." in {$this} line {$this->_line}", 0, E_ERROR, $this->_name, $this->_line, $e);
} catch (\LogicException $e) {
throw new SecurityException($e->getMessage()." in {$this} line {$this->_line}, near '{".$tokens->getSnippetAsString(0,0)."' <- there", 0, 1, $this->_name, $this->_line, $e);
throw new SecurityException($e->getMessage()." in {$this} line {$this->_line}, near '{".$tokens->getSnippetAsString(0,0)."' <- there", 0, E_ERROR, $this->_name, $this->_line, $e);
} catch (\Exception $e) {
throw new CompileException($e->getMessage()." in {$this} line {$this->_line}, near '{".$tokens->getSnippetAsString(0,0)."' <- there", 0, 1, $this->_name, $this->_line, $e);
throw new CompileException($e->getMessage()." in {$this} line {$this->_line}, near '{".$tokens->getSnippetAsString(0,0)."' <- there", 0, E_ERROR, $this->_name, $this->_line, $e);
}
}
@ -277,8 +304,8 @@ class Template extends Render {
return 'echo '.$this->parseExp($tokens).';';
}
if($action === "literal") {
$this->_literal = true;
if($action === "ignore") {
$this->_ignore = true;
$tokens->next();
return '';
}
@ -409,9 +436,6 @@ class Template extends Render {
$_exp .= $tokens->getAndNext();
} elseif($term && !$cond && !$tokens->isLast()) {
if($tokens->is(Tokenizer::MACRO_EQUALS) && $term === 2) {
if($this->_options & Aspect::DENY_SET_VARS) {
throw new \LogicException("Forbidden to set a variable");
}
$_exp .= ' '.$tokens->getAndNext().' ';
$term = 0;
} else {
@ -507,14 +531,34 @@ class Template extends Render {
}
} elseif($t === T_DNUMBER) {
$_var .= '['.substr($tokens->getAndNext(), 1).']';
} elseif($t === "?") {
} elseif($t === "?" || $t === "!") {
$pure_var = false;
$empty = ($t === "?");
$tokens->next();
if($tokens->is(":")) {
$tokens->next();
if($empty) {
return '(empty('.$_var.') ? ('.$this->parseExp($tokens, true).') : '.$_var.')';
} else {
return '(isset('.$_var.') ? '.$_var.' : ('.$this->parseExp($tokens, true).'))';
}
} elseif($tokens->is(Tokenizer::MACRO_BINARY, Tokenizer::MACRO_BOOLEAN, Tokenizer::MACRO_MATH) || !$tokens->valid()) {
if($empty) {
return '!empty('.$_var.')';
} else {
return 'isset('.$_var.')';
}
} else {
$expr1 = $this->parseExp($tokens, true);
if(!$tokens->is(":")) {
throw new UnexpectedException($tokens, null, "ternary operator");
}
$expr2 = $this->parseExp($tokens, true);
if($empty) {
return '(empty('.$_var.') ? '.$expr2.' : '.$expr1;
} else {
return '(isset('.$_var.') ? '.$expr1.' : '.$expr2;
}
}
} elseif($t === "!") {
$pure_var = false;
@ -802,7 +846,7 @@ class Template extends Render {
$params[ $key ] = $this->parseExp($tokens);
} else {
$params[ $key ] = true;
$params[] = "'".$key."'";
$params[] = '"'.$key.'"';
}
} elseif($tokens->is(Tokenizer::MACRO_SCALAR, '"', '`', T_VARIABLE, "[", '(')) {
$params[] = $this->parseExp($tokens);
@ -822,3 +866,4 @@ class Template extends Render {
class CompileException extends \ErrorException {}
class SecurityException extends CompileException {}
class ImproperUseException extends \LogicException {}

View File

@ -6,7 +6,6 @@ defined('T_TRAIT') || define('T_TRAIT', 355);
defined('T_TRAIT_C') || define('T_TRAIT_C', 365);
/**
* This iterator cannot be rewinded.
* Each token have structure
* - Token (constant T_* or text)
* - Token name (textual representation of the token)
@ -596,19 +595,6 @@ class Tokenizer {
return $this->curr ? $this->curr[3] : $this->_last_no;
}
/**
* Dump (append) token into variable
*
* @param mixed $var
* @param bool $whitespace include whitespace
*/
/*public function appendTo(&$var, $whitespace = false) {
$var .= $this->curr[1];
if($whitespace && $this->curr[2]) {
$var .= $this->curr[2];
}
}*/
/**
* Parse code and append tokens. This method move pointer to offset.
* @param string $code
@ -642,20 +628,20 @@ class TokenizeException extends \RuntimeException {}
* Unexpected token
*/
class UnexpectedException extends TokenizeException {
public function __construct(Tokenizer $tokens, $expect = null) {
public function __construct(Tokenizer $tokens, $expect = null, $where = null) {
if($expect && count($expect) == 1 && is_string($expect[0])) {
$expect = ", expect '".$expect[0]."'";
} else {
$expect = "";
}
if(!$tokens->curr) {
$this->message = "Unexpected end of expression$expect";
$this->message = "Unexpected end of ".($where?:"expression")."$expect";
} elseif($tokens->curr[1] === "\n") {
$this->message = "Unexpected new line$expect";
} elseif($tokens->curr[0] === T_WHITESPACE) {
$this->message = "Unexpected whitespace$expect";
} else {
$this->message = "Unexpected token '".$tokens->current()."'$expect";
$this->message = "Unexpected token '".$tokens->current()."' in ".($where?:"expression")."$expect";
}
}
};

View File

@ -13,13 +13,13 @@ class RenderTest extends \PHPUnit_Framework_TestCase {
public static function setUpBeforeClass() {
self::$render = new Render("render.tpl", function ($tpl) {
echo "It is render function ".$tpl["render"];
});
}, array());
}
public function testCreate() {
$r = new Render("test.render.tpl", function () {
echo "Test render";
});
}, array());
$this->assertSame("Test render", $r->fetch(array()));
}

View File

@ -16,15 +16,7 @@ class TemplateTest extends \PHPUnit_Framework_TestCase {
echo "<b>Welcome, {block name='username'}{/block} ({block name='usermail'}{/block})</b>";
}));*/
try {
drop(self::$aspect->compileCode(<<<EOT
{if \$data?}
echo lot
{/if}
<doc></doc>
literal: function () { return 1; } end
asdasd
EOT
)->fetch([]));
} catch(\Exception $e) {
echo $e->getTraceAsString();
drop($e->getMessage(), $e->getFile().":".$e->getLine());
@ -32,10 +24,16 @@ EOT
}
public function setUp() {
self::$aspect = new Aspect();
self::$aspect->storeTemplate(new Render("welcome.tpl", function ($tpl) {
exec("rm -f ".ASPECT_RESOURCES.'/compile/*');
self::$aspect = Aspect::factory(ASPECT_RESOURCES.'/template', ASPECT_RESOURCES.'/compile');
self::$aspect->addTemplate(new Render("welcome.tpl", function ($tpl) {
echo "<b>Welcome, ".$tpl["username"]." (".$tpl["email"].")</b>";
}));
}, array()));
/*$parent = self::$aspect->compileCode('Parent template block1: {block name="bk1"}{/block}
block2: {block "bk2"} default block 2{/block}
{var $bk="bk3"}
block3: {block "$bk"} default block 3{/block} ', "parent.tpl");
self::$aspect->addTemplate($parent);*/
}
@ -102,7 +100,6 @@ EOT
array('hello, {$b[3]$c}!', 'Aspect\CompileException', "Unexpected token '\$c'"),
array('hello, {$b[3]c}!', 'Aspect\CompileException', "Unexpected token 'c'"),
array('hello, {$b.obj->valid()}!', 'Aspect\SecurityException', "Forbidden to call methods", Aspect::DENY_METHODS),
array('hello, {$b = 5}!', 'Aspect\SecurityException', "Forbidden to set a variable", Aspect::DENY_SET_VARS),
);
}
@ -158,7 +155,7 @@ EOT
public static function providerModifiersInvalid() {
return array(
array('Mod: {$lorem|}!', 'Aspect\CompileException', "Unexpected end of expression"),
array('Mod: {$lorem|json_encode}!', 'Aspect\CompileException', "Modifier json_encode not found", Aspect::DENY_INLINE_FUNCS),
array('Mod: {$lorem|str_rot13}!', 'Aspect\CompileException', "Modifier str_rot13 not found", Aspect::DENY_INLINE_FUNCS),
array('Mod: {$lorem|my_encode}!', 'Aspect\CompileException', "Modifier my_encode not found"),
array('Mod: {$lorem|truncate:}!', 'Aspect\CompileException', "Unexpected end of expression"),
array('Mod: {$lorem|truncate:abs}!', 'Aspect\CompileException', "Unexpected token 'abs'"),
@ -213,7 +210,6 @@ EOT
array('If: {$a != 5 => 4} end', 'Aspect\CompileException', "Unexpected token '=>'"),
array('If: {$a + (*6)} end', 'Aspect\CompileException', "Unexpected token '*'"),
array('If: {$a + ( 6} end', 'Aspect\CompileException', "Brackets don't match"),
array('If: {$a = 4} end', 'Aspect\SecurityException', "Forbidden to set a variable", Aspect::DENY_SET_VARS),
);
}
@ -250,7 +246,7 @@ EOT
public static function providerIncludeInvalid() {
return array(
array('Include {include} template', 'Aspect\CompileException', "{include} require 'file' parameter"),
array('Include {include} template', 'Aspect\CompileException', "The tag {include} requires 'file' parameter"),
);
}
@ -292,7 +288,7 @@ EOT
return array(
array('If: {if} block1 {/if} end', 'Aspect\CompileException', "Unexpected end of expression"),
array('If: {if 1} block1 {elseif} block2 {/if} end', 'Aspect\CompileException', "Unexpected end of expression"),
array('If: {if 1} block1 {else} block2 {elseif 0} block3 {/if} end', 'Aspect\CompileException', "Incorrect use of the tag {else if}"),
array('If: {if 1} block1 {else} block2 {elseif 0} block3 {/if} end', 'Aspect\CompileException', "Incorrect use of the tag {elseif}"),
array('If: {if 1} block1 {else} block2 {/if} block3 {elseif 0} end', 'Aspect\CompileException', "Unexpected tag 'elseif' (this tag can be used with 'if')"),
);
}
@ -349,6 +345,67 @@ EOT
);
}
public static function providerTernary() {
$a = array(
"a" => 1,
"em" => "empty",
"empty" => array(
"array" => array(),
"int" => 0,
"string" => "",
"double" => 0.0,
"bool" => false,
),
"nonempty" => array(
"array" => array(1,2),
"int" => 2,
"string" => "abc",
"double" => 0.2,
"bool" => true,
)
);
return array(
// ?
array('{if $a?} right {/if}', $a),
array('{if $unexists?} no way {else} right {/if}', $a),
array('{if $empty.array?} no way {else} right {/if}', $a),
array('{if $empty.int?} no way {else} right {/if}', $a),
array('{if $empty.string?} no way {else} right {/if}', $a),
array('{if $empty.double?} no way {else} right {/if}', $a),
array('{if $empty.bool?} no way {else} right {/if}', $a),
array('{if $empty.unexist?} no way {else} right {/if}', $a),
array('{if $nonempty.array?} right {/if}', $a),
array('{if $nonempty.int?} right {/if}', $a),
array('{if $nonempty.string?} right {/if}', $a),
array('{if $nonempty.double?} right {/if}', $a),
array('{if $nonempty.bool?} right {/if}', $a),
// ?: ...
array('{$a?:"empty"}', $a, "1"),
array('{$unexists?:"empty"}', $a, "empty"),
array('{$empty.array?:"empty"}', $a, "empty"),
array('{$empty.int?:"empty"}', $a, "empty"),
array('{$empty.string?:"empty"}', $a, "empty"),
array('{$empty.double?:"empty"}', $a, "empty"),
array('{$empty.bool?:"empty"}', $a, "empty"),
array('{$empty.unexist?:"empty"}', $a, "empty"),
// ? ... : ....
// !
array('{if $a!} right {/if}', $a),
array('{if $unexists!} no way {else} right {/if}', $a),
array('{if $empty.array!} right {/if}', $a),
array('{if $empty.int!} right {/if}', $a),
array('{if $empty.string!} right {/if}', $a),
array('{if $empty.double!} right {/if}', $a),
array('{if $empty.bool!} right {/if}', $a),
array('{if $empty.unexist!} no way {else} right {/if}', $a),
array('{if $nonempty.array!} right {/if}', $a),
array('{if $nonempty.int!} right {/if}', $a),
array('{if $nonempty.string!} right {/if}', $a),
array('{if $nonempty.double!} right {/if}', $a),
array('{if $nonempty.bool!} right {/if}', $a),
);
}
public static function providerForeach() {
$a = array(
"list" => array(1 => "one", 2 => "two", 3 => "three"),
@ -376,7 +433,7 @@ EOT
public static function providerForeachInvalid() {
return array(
array('Foreach: {foreach} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected end of 'foreach'"),
array('Foreach: {foreach} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected end of tag {foreach}"),
array('Foreach: {foreach $list} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected end of expression"),
array('Foreach: {foreach $list+1 as $e} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token '+'"),
array('Foreach: {foreach array_random() as $e} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token 'array_random'"),
@ -387,7 +444,7 @@ EOT
array('Foreach: {foreach $list => $e} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token '=>'"),
array('Foreach: {foreach $list $k => $e} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token '\$k'"),
array('Foreach: {foreach $list as $k =>} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected end of expression"),
array('Foreach: {foreach last=$l $list as $e } {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token 'last' in 'foreach'"),
array('Foreach: {foreach last=$l $list as $e } {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token 'last' in tag {foreach}"),
array('Foreach: {foreach $list as $e unknown=1} {$e}, {/foreach} end', 'Aspect\CompileException', "Unknown parameter 'unknown'"),
array('Foreach: {foreach $list as $e index=$i+1} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token '+'"),
array('Foreach: {foreach $list as $e first=$f+1} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token '+'"),
@ -395,16 +452,16 @@ EOT
array('Foreach: {foreach $list as $e index=max($i,1)} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token 'max'"),
array('Foreach: {foreach $list as $e first=max($i,1)} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token 'max'"),
array('Foreach: {foreach $list as $e last=max($i,1)} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token 'max'"),
array('Foreach: {foreach $list as $e} {$e}, {foreachelse} {break} {/foreach} end', 'Aspect\CompileException', "Incorrect use of the tag {break}"),
array('Foreach: {foreach $list as $e} {$e}, {foreachelse} {continue} {/foreach} end', 'Aspect\CompileException', "Incorrect use of the tag {continue}"),
array('Foreach: {foreach $list as $e} {$e}, {foreachelse} {break} {/foreach} end', 'Aspect\CompileException', "Improper usage of the tag {break}"),
array('Foreach: {foreach $list as $e} {$e}, {foreachelse} {continue} {/foreach} end', 'Aspect\CompileException', "Improper usage of the tag {continue}"),
);
}
public static function providerLiteral() {
public static function providerIgnores() {
$a = array("a" => "lit. A");
return array(
array('{if 0}none{/if} literal: {$a} end', $a, 'literal: lit. A end'),
array('{if 0}none{/if} literal:{literal} {$a} {/literal} end', $a, 'literal: {$a} end'),
array('{if 0}none{/if} literal:{ignore} {$a} {/ignore} end', $a, 'literal: {$a} end'),
array('{if 0}none{/if} literal: { $a} end', $a, 'literal: { $a} end'),
array('{if 0}none{/if} literal: {
$a} end', $a, 'literal: { $a} end'),
@ -443,7 +500,7 @@ EOT
return array(
array('Switch: {switch}{case 1} one {break}{/switch} end', 'Aspect\CompileException', "Unexpected end of expression"),
array('Switch: {switch 1}{case} one {break}{/switch} end', 'Aspect\CompileException', "Unexpected end of expression"),
array('Switch: {switch 1}{break}{case} one {/switch} end', 'Aspect\CompileException', "Incorrect use of the tag {break}"),
array('Switch: {switch 1}{break}{case} one {/switch} end', 'Aspect\CompileException', "Improper usage of the tag {break}"),
);
}
@ -496,8 +553,8 @@ EOT
array('For: {for first=$i $a=3 to=6} block1 {/for} end', 'Aspect\CompileException', "Unexpected token 'first'"),
array('For: {for last=$i $a=3 to=6} block1 {/for} end', 'Aspect\CompileException', "Unexpected token 'last'"),
array('For: {for $a=4 to=6 unk=4} block1 {/for} end', 'Aspect\CompileException', "Unknown parameter 'unk'"),
array('For: {for $a=4 to=6} $a: {$a}, {forelse} {break} {/for} end', 'Aspect\CompileException', "Incorrect use of the tag {break}"),
array('For: {for $a=4 to=6} $a: {$a}, {forelse} {continue} {/for} end', 'Aspect\CompileException', "Incorrect use of the tag {continue}"),
array('For: {for $a=4 to=6} $a: {$a}, {forelse} {break} {/for} end', 'Aspect\CompileException', "Improper usage of the tag {break}"),
array('For: {for $a=4 to=6} $a: {$a}, {forelse} {continue} {/for} end', 'Aspect\CompileException', "Improper usage of the tag {continue}"),
);
}
@ -509,19 +566,20 @@ EOT
array('Layers: {for $a=4 to=6} block1 {if 1} {/for} {/if} end', 'Aspect\CompileException', "Unexpected closing of the tag 'for'"),
array('Layers: {switch 1} {if 1} {case 1} {/if} {/switch} end', 'Aspect\CompileException', "Unexpected tag 'case' (this tag can be used with 'switch')"),
array('Layers: {/switch} end', 'Aspect\CompileException', "Unexpected closing of the tag 'switch'"),
array('Layers: {if 1} end', 'Aspect\CompileException', "Unclosed tags: if"),
array('Layers: {if 1} end', 'Aspect\CompileException', "Unclosed block tags: if"),
);
}
/*public static function providerExtends() {
public static function providerExtends() {
return array(
array('{extends file="proto.tpl"}{block name="bk1"} block1 {/block}', "Template extended by block1"),
array('{extends file="proto.tpl"}{block name=bk1} block1 {/block}', "Template extended by block1"),
array('{extends "proto.tpl"}{block "bk1"} block1 {/block}', "Template extended by block1"),
array('{extends "proto.tpl"}{block "bk1"} block1 {/block}{block "bk2"} block2 {/block} garbage', "Template extended by block1"),
array('{extends "proto2.tpl"}{block "bk1"} block1 {/block}{block "bk2"} block2 {/block} garbage', "Template multi-extended by block1"),
array('{extends file="parent.tpl"}{block name="bk1"} block1 {/block}', "Template extended by block1"),
array('{extends "parent.tpl"}{block "bk1"} block1 {/block}', "Template extended by block1"),
array('{extends "parent.tpl"}{block "bk1"} block1 {/block}{block "bk2"} block2 {/block} garbage', "Template extended by block1"),
array('{extends file="parent.tpl"}{block "bk1"} block1 {/block}{block "bk2"} block2 {/block} garbage', "Template multi-extended by block1"),
array('{extends "parent.tpl"}{block "bk1"} block1 {/block}{block "bk2"} block2 {/block} {block "bk3"} block3 {/block} garbage', "Template multi-extended by block1"),
array('{extends "parent.tpl"}{var $bk = "bk3"}{block "bk1"} block1 {/block}{block "bk2"} block2 {/block} {block "$bk"} block3 {/block} garbage', "Template multi-extended by block1"),
);
}*/
}
public function exec($code, $vars, $result, $dump = false) {
$tpl = self::$aspect->compileCode($code, "inline.tpl");
@ -629,6 +687,14 @@ EOT
$this->execError($code, $exception, $message, $options);
}
/**
* @group ternary
* @dataProvider providerTernary
*/
public function testTernary($code, $vars, $result = 'right') {
$this->exec(__FUNCTION__.": $code end", $vars, __FUNCTION__.": $result end");
}
/**
* @dataProvider providerForeach
*/
@ -658,9 +724,9 @@ EOT
}
/**
* @dataProvider providerLiteral
* @dataProvider providerIgnores
*/
public function testLiterals($code, $vars, $result) {
public function testIgnores($code, $vars, $result) {
$this->exec($code, $vars, $result);
}
@ -700,10 +766,24 @@ EOT
}
/**
* @dataProvider providerExtends
* @group extends
*/
public function _testExtends($code, $exception, $message, $options = 0) {
$this->execError($code, $exception, $message, $options);
public function _testExtends() {
echo(self::$aspect->getTemplate("parent.tpl")->getBody()); exit;
}
/**
* @group extends
*/
public function testExtends() {
echo(self::$aspect->getTemplate("child1.tpl")->getBody()); exit;
}
/**
* @group extends
*/
public function __testExtends() {
echo(self::$aspect->fetch("child1.tpl", array("a" => "value", "z" => ""))."\n"); exit;
}
}

View File

@ -55,7 +55,7 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase {
$tokens->skip(T_STRING)->skip('(')->skip(':');
} catch(\Exception $e) {
$this->assertInstanceOf('Aspect\UnexpectedException', $e);
$this->assertStringStartsWith("Unexpected token '3', expect ':'", $e->getMessage());
$this->assertStringStartsWith("Unexpected token '3' in expression, expect ':'", $e->getMessage());
}
$this->assertTrue($tokens->valid());
$this->assertSame("3", $tokens->current());

View File

@ -33,11 +33,11 @@ class AspectTest extends \PHPUnit_Framework_TestCase {
public function testAddRender() {
$test = $this;
$this->aspect->storeTemplate(new Render('render.tpl', function($tpl) use ($test) {
$this->aspect->addTemplate(new Render('render.tpl', function($tpl) use ($test) {
/** @var \PHPUnit_Framework_TestCase $test */
$test->assertInstanceOf('Aspect\Render', $tpl);
echo "Inline render";
}));
}, array()));
$this->assertSame("Inline render", $this->aspect->fetch('render.tpl', array()));
}
@ -85,15 +85,15 @@ class AspectTest extends \PHPUnit_Framework_TestCase {
}
public function testSetModifier() {
$this->aspect->setModifier("mymod", "myMod");
$this->aspect->addModifier("mymod", "myMod");
$this->tpl('Custom modifier {$a|mymod}');
$this->assertSame("Custom modifier (myMod)Custom(/myMod)", $this->aspect->fetch('custom.tpl', array("a" => "Custom")));
}
public function testSetFunctions() {
$this->aspect->setForceCompile(true);
$this->aspect->setFunction("myfunc", "myFunc");
$this->aspect->setBlockFunction("myblockfunc", "myBlockFunc");
$this->aspect->addFunction("myfunc", "myFunc");
$this->aspect->addBlockFunction("myblockfunc", "myBlockFunc");
$this->tpl('Custom function {myfunc name="foo"}');
$this->assertSame("Custom function MyFunc:foo", $this->aspect->fetch('custom.tpl', array()));
$this->tpl('Custom function {myblockfunc name="foo"} this block1 {/myblockfunc}');
@ -102,11 +102,8 @@ class AspectTest extends \PHPUnit_Framework_TestCase {
public function testSetCompilers() {
$this->aspect->setForceCompile(true);
$this->aspect->setCompiler("mycompiler", 'myCompiler');
$this->aspect->setBlockCompiler("myblockcompiler", array(
'open' => 'myBlockCompilerOpen',
'close' => 'myBlockCompilerClose'
), array(
$this->aspect->addCompiler("mycompiler", 'myCompiler');
$this->aspect->addBlockCompiler("myblockcompiler", 'myBlockCompilerOpen', 'myBlockCompilerClose', array(
'tag' => 'myBlockCompilerTag'
));
$this->tpl('Custom compiler {mycompiler name="bar"}');

View File

@ -0,0 +1,4 @@
{extends "parent.tpl"}
{block blk1}
<b>blk1.{$a}</b>
{/block}

View File

@ -0,0 +1,4 @@
{extends "parent.tpl"}
{block blk2}
<b>blk2.{$a}</b>
{/block}

View File

@ -0,0 +1,9 @@
{extends "parent.tpl"}
{block blk1}
<b>blk1.{$a} (rewrited)</b>
{/block}
{block blk3}
<b>blk3.{$a}</b>
{/block}

View File

@ -0,0 +1,4 @@
Parent template
Block1: {block blk1}{/block}
Block2: {block blk2}{/block}
Block3: {block blk3}default{/block}